-
Notifications
You must be signed in to change notification settings - Fork 1.7k
improve sse performance #2851
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
improve sse performance #2851
Conversation
|
@saddlepaddle is attempting to deploy a commit to the Onlook Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughRefactors chat from a provider/context model to a hook-driven, prop-based architecture; introduces useChat, centralizes chat UI in ChatTabContent, replaces client-side message storage with server replaceConversationMessages persistence, rewires editor chat APIs and tool handling, and removes legacy suggestion/error managers. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant CI as ChatInput
participant CT as ChatTabContent / useChat
participant AI as AI Transport (/api/chat)
participant TL as Tool Handler
participant API as TRPC Chat API
participant ED as EditorEngine
U->>CI: Type + Submit
CI->>CT: onSendMessage(content, type)
CT->>CT: Append local user message (buffer)
CT->>AI: stream({ conversationId, projectId, chatType, context })
AI-->>CT: streaming parts / tokens
alt Tool call emitted
AI-->>CT: toolCall(payload)
CT->>TL: handleToolCall(toolCall, editorEngine, addToolResult)
TL-->>CT: addToolResult(result)
end
AI-->>CT: onFinish(finalMessages, metadata)
CT->>API: replaceConversationMessages({ conversationId, messages })
API-->>CT: ok
CT->>API: generate suggestions (last msgs)
API-->>CT: suggestions
CT->>ED: setConversationLength / set isStreaming=false
CT-->>CI: update UI (messages, suggestions)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
Outdated
Show resolved
Hide resolved
9d806a5 to
e4ab69f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/web/client/src/components/store/editor/version/index.ts (1)
53-64: Fix concurrency guard: isSaving never gates work.
this.isSavingis only set totrueafter the commit succeeds, so concurrent calls are not prevented. Set it totrueimmediately after the initial check and remove the late assignment.Apply:
try { if (this.isSaving) { if (showToast) { toast.error('Backup already in progress'); } return { success: false, commit: null, errorReason: CreateCommitFailureReason.COMMIT_IN_PROGRESS, }; } + // Gate concurrent saves + this.isSaving = true; @@ - this.isSaving = true;Also applies to: 122-148
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)
148-149: Internationalize user-facing strings.Hardcoded strings (“Failed to send message…”, “Ask a question about your project…”, “No active frame…”, “Failed to capture screenshot…”, “Stop response”) should use next-intl per repo guidelines.
Also applies to: 155-157, 233-235, 258-259, 271-272, 418-418
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
41-48: Replace hardcoded strings with next-intl and fix invalid Tailwind classtext-red.
- Move the user-facing strings in apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (lines 41–48 and 55–58) into next-intl messages and use useTranslations(); add keys in apps/web/client/messages/*.json.
- Replace the
text-redclass with a valid utility or semantic token (e.g.text-red-500,text-destructive, ortext-foreground). No.text-redCSS or Tailwind mapping was found in packages/ui/tailwind.config.ts or packages/ui/src/globals.css.
🧹 Nitpick comments (39)
apps/web/client/src/components/store/editor/chat/context.ts (2)
20-20: Make editorEngine immutable.
Prevent accidental reassignment by marking it readonly.Apply this diff:
- constructor(private editorEngine: EditorEngine) { + constructor(private readonly editorEngine: EditorEngine) {
44-47: Remove redundant await.
Returning the Promise is cleaner; no behavior change.async getLatestContext(): Promise<MessageContext[]> { const context = await this.getChatContext(); - return await this.getRefreshedContext(context); + return this.getRefreshedContext(context); }apps/web/client/src/components/tools/tools.ts (5)
186-191: Tighten types: prefer unknown over any in API surfaceUse unknown for output and the addToolResult contract to comply with “no any” guideline and improve type safety.
-export async function handleToolCall(toolCall: ToolCall<string, unknown>, editorEngine: EditorEngine, addToolResult: (toolResult: { tool: string, toolCallId: string, output: any }) => Promise<void>) { +export async function handleToolCall( + toolCall: ToolCall<string, unknown>, + editorEngine: EditorEngine, + addToolResult: (toolResult: { tool: string; toolCallId: string; output: unknown }) => Promise<void> +) { const toolName = toolCall.toolName; const currentChatMode = editorEngine.state.chatMode; const availableTools = getToolSetFromType(currentChatMode); - let output: any = null; + let output: unknown = null;
202-208: Validate tool input against its Zod schema before invoking handlerYou store inputSchema but don’t use it. Parse inputs to fail fast with clear errors and avoid handler-level runtime crashes.
const clientTool = TOOL_HANDLERS[toolName]; if (!clientTool) { throw new Error(`Unknown tool call: ${toolName}`); } - output = await clientTool.handler(toolCall.input, editorEngine); + const parsed = clientTool.inputSchema.safeParse(toolCall.input); + if (!parsed.success) { + throw new Error(`Invalid input for tool "${toolName}": ${parsed.error.message}`); + } + output = await clientTool.handler(parsed.data, editorEngine);
209-209: Harden error serialization and surface diagnosticsString concatenation with a non-Error yields “[object Object]”. Serialize safely and log.
- } catch (error) { - output = 'error handling tool call ' + error; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + output = `error handling tool call: ${message}`; + console.error('handleToolCall error', { tool: toolName, error });
211-215: Don’t silently drop addToolResult rejectionsvoid marks intent but a rejection remains unhandled. Either await or explicitly catch.
- void addToolResult({ + void addToolResult({ tool: toolName, toolCallId: toolCall.toolCallId, output: output, - }); + }).catch((err) => { + console.error('addToolResult failed', err); + });
64-68: Replace lingering any with Zod-aware genericsThis interface leaks any in multiple places. Use ZodTypeAny + inference to keep handlers precisely typed.
-interface ClientToolMap extends Record<string, { - name: string; - inputSchema: z.ZodObject<any>; - handler: (args: any, editorEngine: EditorEngine) => Promise<any>; -}> { } +type ToolDef<T extends z.ZodTypeAny, R = unknown> = { + name: string; + inputSchema: T; + handler: (args: z.infer<T>, editorEngine: EditorEngine) => Promise<R>; +}; +type ClientToolMap = Record<string, ToolDef<z.ZodTypeAny, unknown>>;apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (5)
71-72: Avoid MobX store objects in effect deps to prevent spurious reruns.Depending on
editorEngine.chat.conversation(an object) can cause unnecessary reruns or loops. The effect only needsconversations.- }, [editorEngine.chat.conversation, conversations]); + }, [conversations]);
79-79: Non‑awaited call is okay; consider unmount/dup guards.
void resumeCreate(...)is fine to fire‑and‑forget. To avoid side‑effects after unmount and double toasts, optionally gate with anisMountedref or anAbortController.
89-99: Harden image context mapping; avoid hard‑coded display text.
- Add a safe fallback for
mimeType.- "user image" may surface in UI; per guidelines avoid hardcoded user‑facing text—route through next‑intl or centralize as a message key if this is presented to users.
const imageContexts: ImageMessageContext[] = creationData.context .filter((context) => context.type === CreateRequestContextType.IMAGE) .map((context) => ({ type: MessageContextType.IMAGE, content: context.content, - mimeType: context.mimeType, + mimeType: context.mimeType ?? 'image/png', displayName: 'user image', }));Would you confirm whether
displayNameis ever rendered to users? If yes, we should internationalize it.
103-106: Normalize the prompt and skip empty fragments.Trimming and filtering prevents stray newlines and empty strings.
- const prompt = creationData.context - .filter((context) => context.type === CreateRequestContextType.PROMPT) - .map((context) => context.content) - .join('\n'); + const prompt = creationData.context + .filter((c) => c.type === CreateRequestContextType.PROMPT) + .map((c) => c.content?.trim()) + .filter(Boolean) + .join('\n\n');
108-117: Drop redundant fetch/select; reuse the already‑selected conversation.applyConversations already selects (or starts) the first conversation — re‑fetching + re‑selecting adds latency and can race with that effect. Replace the getConversations/selectConversation block with a direct use of the conversation manager's current conversation and then send the message:
const conversation = editorEngine.chat.conversation.current;
if (!conversation) throw new Error('No conversation found');
await editorEngine.chat.sendMessage(prompt, ChatType.CREATE);apps/web/client/src/components/store/editor/chat/conversation.ts (2)
44-51: Avoid replacing observable object for a single-field update.Mutate
messageCountto preserve referential stability and reduce unnecessary reactions.Apply:
- if (this.current) { - this.current = { - ...this.current, - messageCount: length, - }; - } + if (this.current) { + this.current.messageCount = length; + }Optional: rename to
setMessageCountfor clarity.
85-87: Resetting messageCount to 0 on select may cause UI flicker.Consider tracking a separate
isLoadingMessagesflag, or preserve the last known count until data arrives.apps/web/client/src/app/api/chat/route.ts (2)
74-76: Use a broader‑compat fallback instead offindLast.
Array.prototype.findLastrequires newer runtimes. Prefer a reverse scan to avoid Node version pitfalls.- const lastUserMessage = messages.findLast((message) => message.role === 'user'); + const lastUserMessage = + [...messages].reverse().find((message) => message.role === 'user');
139-150: Add guardrails to onFinish persistence; 'tool' role is not supportedWrap the persistence call in try/catch, cap it with a short timeout, and log errors so persistence failures don’t break or slow the completed stream. The repo’s DB/schema and routers use only 'user'|'assistant'|'system' (no 'tool'), so filtering out tool messages is correct — decide separately whether to persist 'system' messages.
- onFinish: async ({ messages: finalMessages }) => { - const messagesToStore = finalMessages - .filter(msg => - (msg.role === 'user' || msg.role === 'assistant') - ) - .map(msg => toDbMessage(msg, conversationId)); - - await api.chat.message.replaceConversationMessages({ - conversationId, - messages: messagesToStore, - }); - }, + onFinish: async ({ messages: finalMessages }) => { + try { + const messagesToStore = finalMessages + .filter((msg) => msg.role === 'user' || msg.role === 'assistant') + .map((msg) => toDbMessage(msg as ChatMessage, conversationId)); + + // Avoid long tail: cap persistence time + const persist = api.chat.message.replaceConversationMessages({ + conversationId, + messages: messagesToStore, + }); + const withTimeout = Promise.race([ + persist, + new Promise((_, rej) => + setTimeout(() => rej(new Error('persist timeout')), 5000), + ), + ]); + await withTimeout; + } catch (e) { + // Log but don’t fail the request after streaming completed + console.error('onFinish persistence error', e); + } + },apps/web/client/src/app/api/chat/helpers/stream.ts (1)
9-24: Collapse duplicate branches and make model selection configurable.All switch branches call initModel with OPENROUTER_MODELS.OPEN_AI_GPT_5 — replace the switch with a single initModel call, keep the chatType parameter for future per-type mapping, and read the model id from config/env (fallback to OPENROUTER_MODELS.OPEN_AI_GPT_5). File: apps/web/client/src/app/api/chat/helpers/stream.ts — getModelFromType.
apps/web/client/src/components/store/editor/version/index.ts (2)
134-141: Avoid logging unsanitized commit messages to analytics.Use the sanitized message in the failure event as well to prevent leaking raw content.
- this.editorEngine.posthog.capture('versions_create_commit_failed', { - message, + const safeMessage = sanitizeCommitMessage(message); + this.editorEngine.posthog.capture('versions_create_commit_failed', { + message: safeMessage, error: error instanceof Error ? error.message : 'Unknown error', });
56-57: Localize user-facing toast strings per app guidelines.These toasts live under apps/web/client/src and should use next-intl instead of hardcoded English.
If localization from store code is awkward, consider routing toast invocation through a UI layer or a small i18nToast helper that accepts translation keys.
Also applies to: 74-75, 88-89, 105-106, 118-121, 135-136, 215-216, 220-231, 267-269, 294-299, 311-323
apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (1)
31-34: Localize toast messages.Follow the “Avoid hardcoded user-facing text” rule; use next-intl.
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx (2)
98-111: Use stable keys for suggestion buttons.
key={suggestion.title}can collide; prefer a stable unique key (id if available, orindexas fallback).- key={suggestion.title} + key={`${index}-${suggestion.title}`}
98-136: Small a11y improvement: label suggestion buttons.Add an aria-label to improve screen reader output.
- <motion.button + <motion.button + aria-label={`Suggestion: ${suggestion.title}`}apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (2)
19-38: Localize the “Thinking …” text.Avoid hardcoded strings; use next-intl.
+import { useTranslations } from 'next-intl'; +import { transKeys } from '@/i18n/keys'; @@ export const StreamMessage = ({ @@ }) => { + const t = useTranslations(); @@ <div className="flex w-full h-full flex-row items-center gap-2 px-4 my-2 text-small content-start text-foreground-secondary"> <Icons.LoadingSpinner className="animate-spin" /> - <p>Thinking ...</p> + <p>{t(transKeys.editor.chat.thinking)}</p> </div>
14-17: Drop unnecessary useMemo.
const isAssistantStreamMessage = streamMessage?.role === 'assistant'is sufficient.apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx (2)
75-77: Localize “Code” tab label.Replace the hardcoded string with a translation key.
- Code + {t(transKeys.editor.panels.edit.tabs.dev.name)}
84-90: Empty CHAT tab when no conversation.UI shows nothing if
currentConversationis null. Consider a lightweight empty state (localized) or a CTA to start a conversation.apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (3)
83-91: Localize all user-facing strings.Toasts, buttons, and tooltips should use next-intl per repo rules.
Also applies to: 95-103, 143-149, 169-171, 185-187, 203-206, 257-258
218-221: Avoid nanoid as list keys.Random keys cause unnecessary remounts; use a stable identifier from
context(orindexas a last resort).- {message.metadata?.context?.map((context) => ( - <SentContextPill key={nanoid()} context={context} /> + {message.metadata?.context?.map((context, idx) => ( + <SentContextPill key={context.id ?? idx} context={context} /> ))}
78-82: Preserve edit UI on failure.
handleSubmitcloses edit mode before the async call. Only close on success and allow retry on failure. Also, return the promise fromsendMessageto make it awaitable.- const handleSubmit = async () => { - setIsEditing(false); - await sendMessage(editValue); - }; + const handleSubmit = async () => { + try { + await sendMessage(editValue); + setIsEditing(false); + } catch { + // keep editing; toast already shown + } + }; @@ - const sendMessage = async (newContent: string) => { - toast.promise( + const sendMessage = async (newContent: string) => { + return toast.promise( onEditMessage(message.id, newContent, ChatType.EDIT), { loading: 'Editing message...', success: 'Message resubmitted successfully', error: 'Failed to resubmit message', } - ) + ); };Also applies to: 94-103
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (2)
10-13:enabled: !!conversationIdis redundant with a required string prop.Since
conversationId: stringis non-optional,!!conversationIdis always true (unless empty string is passed). Either makeconversationIdoptional in props or drop theenabledguard.
15-17: Avoid returning null during load; render a lightweight placeholder.Returning
nullcollapses layout and can cause focus jumps. Consider a skeleton or a 0-height placeholder to stabilize the panel.apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
18-35: Be defensive when parsing error.message JSON.Parsing
error.messageis brittle. Consider checkingerror.causeor a structured field first, and only parse when it looks like JSON (e.g., starts with{). Current try/catch is OK, but a small guard reduces noise.apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)
67-76: Fix stale closure in focus handler (FOCUS_CHAT_INPUT_EVENT).
focusHandlerreadsisStreamingfrom initial render due to an empty dependency array. Use a ref to keep the latest value and avoid focusing while streaming.const focusInput = () => { requestAnimationFrame(() => { textareaRef.current?.focus(); }); }; + // Keep latest streaming state for event handlers registered once + const isStreamingRef = useRef(isStreaming); + useEffect(() => { + isStreamingRef.current = isStreaming; + }, [isStreaming]); + useEffect(() => { const focusHandler = () => { - if (textareaRef.current && !isStreaming) { + if (textareaRef.current && !isStreamingRef.current) { focusInput(); } }; window.addEventListener(FOCUS_CHAT_INPUT_EVENT, focusHandler); return () => window.removeEventListener(FOCUS_CHAT_INPUT_EVENT, focusHandler); }, []); - const disabled = isStreaming + const disabled = isStreaming;Also applies to: 95-95
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
56-61: EnsureisExecutingToolCallresets even on unexpected failures.Prefer
.finallyto guarantee reset.- onToolCall: async (toolCall) => { - setIsExecutingToolCall(true); - void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => { - setIsExecutingToolCall(false); - }); - }, + onToolCall: async (toolCall) => { + setIsExecutingToolCall(true); + void handleToolCall(toolCall.toolCall, editorEngine, addToolResult) + .finally(() => setIsExecutingToolCall(false)); + },apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (1)
6-16: Preserve text boundaries when flattening parts.Joining with '' can smash words. Filter non-text parts and join with '\n' (or a space) to retain readability.
export const prepareMessagesForSuggestions = (messages: ChatMessage[]) => { - return messages.slice(-5).map((message) => ({ + return messages.slice(-5).map((message) => ({ role: message.role, - content: message.parts.map((p) => { - if (p.type === 'text') { - return p.text; - } - return ''; - }).join(''), + content: message.parts + .filter((p) => p.type === 'text') + .map((p) => p.text) + .join('\n'), })); };apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2)
23-44: Prefer stable keys; drop index from key.Using both id and index can cause unnecessary remounts.
- return <div key={`message-${message.id}-${index}`}>{messageNode}</div>; + return <div key={message.id}>{messageNode}</div>;
59-63: Optimize ChatMessageList contentKey and localize StreamMessage text
Replace the expensive contentKey in apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx — current:
contentKey={${messages.map((m) => m.id).join('|')}${isStreaming ?|${messages?.[messages.length - 1]?.id ?? ''}: ''}}. Use a lighter stable key (example):contentKey={${messages.length ? messages[messages.length - 1].id : 'empty'}|${messages.length}|${isStreaming ? '1' : '0'}}to avoid an O(n) map/join on every render while preserving change detection.Localize the hardcoded "Thinking ..." in apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx — import
useTranslationsandtransKeysand replace<p>Thinking ...</p>with a translation call (e.g.<p>{t(transKeys.editor.panels.edit.tabs.chat.streaming)}</p>) and add the key to locale files.apps/web/client/src/components/store/editor/chat/index.ts (2)
13-16: Make chatSendMessage non-observable.It’s a callback, not state. Prevent MobX from tracking it.
- makeAutoObservable(this); + makeAutoObservable(this, { chatSendMessage: false });
39-49: Return the ChatMessage (or its id) from ChatManager.sendMessageChange the signature to
async sendMessage(content: string, type: ChatType): Promise<ChatMessage>and return the result ofthis.chatSendMessage(content, type)instead of discarding it.Location: apps/web/client/src/components/store/editor/chat/index.ts (sendMessage). Call sites observed: apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (awaits), apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (void call), apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (setChatActions wiring).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (34)
apps/web/client/src/app/api/chat/helpers/stream.ts(1 hunks)apps/web/client/src/app/api/chat/route.ts(2 hunks)apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx(14 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx(5 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx(6 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx(3 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx(3 hunks)apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx(0 hunks)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts(1 hunks)apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx(3 hunks)apps/web/client/src/app/project/[id]/providers.tsx(1 hunks)apps/web/client/src/components/store/editor/chat/context.ts(3 hunks)apps/web/client/src/components/store/editor/chat/conversation.ts(4 hunks)apps/web/client/src/components/store/editor/chat/error.ts(0 hunks)apps/web/client/src/components/store/editor/chat/helpers.ts(0 hunks)apps/web/client/src/components/store/editor/chat/index.ts(2 hunks)apps/web/client/src/components/store/editor/chat/message.ts(0 hunks)apps/web/client/src/components/store/editor/chat/suggestions.ts(0 hunks)apps/web/client/src/components/store/editor/version/index.ts(1 hunks)apps/web/client/src/components/tools/tools.ts(1 hunks)apps/web/client/src/server/api/routers/chat/message.ts(1 hunks)packages/ui/src/components/chat/hooks/use-auto-scroll.tsx(0 hunks)
💤 Files with no reviewable changes (6)
- apps/web/client/src/components/store/editor/chat/suggestions.ts
- packages/ui/src/components/chat/hooks/use-auto-scroll.tsx
- apps/web/client/src/components/store/editor/chat/message.ts
- apps/web/client/src/components/store/editor/chat/helpers.ts
- apps/web/client/src/components/store/editor/chat/error.ts
- apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/client/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env insteadAvoid hardcoded user-facing text; use next-intl messages/hooks
Files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsxapps/web/client/src/app/project/[id]/providers.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks insteadUse path aliases @/* and ~/* for imports mapping to src/*
Files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsxapps/web/client/src/app/api/chat/route.tsapps/web/client/src/app/project/[id]/providers.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/api/chat/helpers/stream.tsapps/web/client/src/app/project/[id]/_hooks/use-chat/utils.tsapps/web/client/src/server/api/routers/chat/message.tsapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsxapps/web/client/src/components/store/editor/version/index.tsapps/web/client/src/components/tools/tools.tsapps/web/client/src/app/project/[id]/_components/right-panel/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsxapps/web/client/src/components/store/editor/chat/context.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/components/store/editor/chat/index.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsxapps/web/client/src/components/store/editor/chat/conversation.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsxapps/web/client/src/app/project/[id]/providers.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use the any type unless necessary
Files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsxapps/web/client/src/app/api/chat/route.tsapps/web/client/src/app/project/[id]/providers.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/api/chat/helpers/stream.tsapps/web/client/src/app/project/[id]/_hooks/use-chat/utils.tsapps/web/client/src/server/api/routers/chat/message.tsapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsxapps/web/client/src/components/store/editor/version/index.tsapps/web/client/src/components/tools/tools.tsapps/web/client/src/app/project/[id]/_components/right-panel/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsxapps/web/client/src/components/store/editor/chat/context.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/components/store/editor/chat/index.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsxapps/web/client/src/components/store/editor/chat/conversation.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsxapps/web/client/src/app/api/chat/route.tsapps/web/client/src/app/project/[id]/providers.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/api/chat/helpers/stream.tsapps/web/client/src/app/project/[id]/_hooks/use-chat/utils.tsapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using the any type unless absolutely necessary
Files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsxapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsxapps/web/client/src/app/api/chat/route.tsapps/web/client/src/app/project/[id]/providers.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/api/chat/helpers/stream.tsapps/web/client/src/app/project/[id]/_hooks/use-chat/utils.tsapps/web/client/src/server/api/routers/chat/message.tsapps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsxapps/web/client/src/components/store/editor/version/index.tsapps/web/client/src/components/tools/tools.tsapps/web/client/src/app/project/[id]/_components/right-panel/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsxapps/web/client/src/components/store/editor/chat/context.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/components/store/editor/chat/index.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsxapps/web/client/src/components/store/editor/chat/conversation.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/server/api/routers/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures
apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization
Files:
apps/web/client/src/server/api/routers/chat/message.ts
🧠 Learnings (3)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/**/*.tsx : observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Applied to files:
apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
🧬 Code graph analysis (20)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
useChat(36-228)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (1)
ChatMessages(23-65)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx (1)
ErrorSection(19-118)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)
ChatInput(33-434)
apps/web/client/src/app/api/chat/route.ts (2)
packages/db/src/mappers/chat/message.ts (1)
toDbMessage(17-34)apps/web/client/src/trpc/react.tsx (1)
api(23-23)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (6)
packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/components/tools/tools.ts (1)
handleToolCall(186-218)apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (3)
getUserChatMessageFromString(18-35)prepareMessagesForSuggestions(6-16)attachCommitToUserMessage(38-68)apps/web/client/src/trpc/react.tsx (1)
api(23-23)
apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (5)
packages/db/src/schema/chat/message.ts (1)
messages(11-27)packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)packages/models/src/chat/message/context.ts (1)
MessageContext(53-59)packages/git/src/git.ts (1)
GitCommit(19-25)apps/web/client/src/trpc/react.tsx (1)
api(23-23)
apps/web/client/src/server/api/routers/chat/message.ts (2)
apps/web/client/src/server/api/trpc.ts (1)
protectedProcedure(130-149)packages/db/src/schema/chat/message.ts (2)
messageInsertSchema(29-29)messages(11-27)
apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (1)
apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/helpers.ts (1)
DEFAULT_INPUT_STATE(8-13)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (2)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
applyConversations(20-28)packages/models/src/chat/message/context.ts (2)
MessageContext(53-59)ImageMessageContext(38-41)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (2)
packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)packages/db/src/schema/chat/message.ts (1)
messages(11-27)
apps/web/client/src/components/tools/tools.ts (3)
apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)packages/ai/src/tools/toolset.ts (1)
getToolSetFromType(69-71)packages/ui/src/components/sonner.tsx (1)
toast(19-19)
apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (1)
ChatTab(9-28)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx (2)
packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (1)
MessageContext(53-59)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (2)
apps/web/client/src/components/store/state/index.ts (1)
useStateManager(6-6)packages/models/src/chat/response.ts (1)
Usage(1-5)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (2)
apps/web/client/src/trpc/react.tsx (1)
api(23-23)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx (1)
ChatTabContent(13-44)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
EditMessage(19-23)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
UserMessage(29-264)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
StreamMessage(6-41)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
ErrorMessage(11-63)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
SendMessage(18-18)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)apps/web/client/src/components/store/editor/chat/index.ts (1)
sendMessage(43-49)
apps/web/client/src/components/store/editor/chat/index.ts (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
ConversationManager(11-128)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
SendMessage(18-18)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx (3)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
SendMessage(18-18)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)packages/ui/src/components/sonner.tsx (1)
toast(19-19)
apps/web/client/src/components/store/editor/chat/conversation.ts (3)
packages/models/src/chat/conversation/index.ts (1)
ChatConversation(3-12)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)apps/web/client/src/trpc/react.tsx (1)
api(23-23)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (4)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
EditMessage(19-23)packages/models/src/chat/message/message.ts (1)
ChatMessage(17-17)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)packages/ui/src/components/sonner.tsx (1)
toast(19-19)
apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
Show resolved
Hide resolved
| const sendFixError = () => { | ||
| toast.promise( | ||
| onSendMessage('How can I resolve these errors? If you propose a fix, please make it concise.', ChatType.FIX), | ||
| { | ||
| error: 'Failed to send fix error message. Please try again.', | ||
| } | ||
| ) | ||
| }; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Externalize user-facing strings (next-intl) and add toast states.
Several strings are hardcoded (“How can I resolve…”, “Failed to send…”, “Error/Errors”, “You have … errors”, “Fix”). Per guidelines, move them to next-intl. Also, provide loading/success messages to toast.promise for clearer UX.
Also applies to: 65-71, 83-85
| conversation.id === | ||
| editorEngine.chat.conversation.current?.conversation.id && | ||
| editorEngine.chat.conversation.current?.id && | ||
| 'bg-background-onlook text-primary font-semibold', | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Correct: comparison now targets current?.id.
This matches the new shape and fixes the highlight check. Consider sanity‑checking for any remaining references to current?.conversation.id.
🏁 Script executed:
#!/bin/bash
# Find any remaining usages of the old shape `.conversation.current?.conversation.id`
rg -nP --type=ts --type=tsx -C2 '\.conversation\.current\?\.\s*conversation\b'Length of output: 27
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching repo for remaining usages of the old `.conversation.current?.conversation.id` shape..."
PATTERNS=(
'\.conversation\.current\?\.\s*conversation\b'
'\.conversation\.current\?\.\s*conversation\s*\.\s*id\b'
'\.conversation\.current\?\.\s*id\b'
'\.conversation\.current\.\s*conversation\b'
'\.conversation\.current\.\s*conversation\s*\.\s*id\b'
'\bcurrent\?\.\s*conversation\b'
'\bcurrent\?\.\s*conversation\s*\.\s*id\b'
'editorEngine\.chat\.conversation\.current\?'
)
for p in "${PATTERNS[@]}"; do
echo "=== PATTERN: $p ==="
rg -n -C2 --hidden -S "$p" -g '**/*.ts' -g '**/*.tsx' -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true
done
echo "Search complete."Length of output: 5102
Fix remaining .conversation.current?.conversation.id usage — change to .conversation.current?.id.
Search found editorEngine.chat.conversation.current?.conversation.id in apps/web/client/src/components/tools/handlers/edit.ts:151 — replace with editorEngine.chat.conversation.current?.id (or call the store getter getCurrentConversationId()).
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
around lines 78 to 81, the code is using
editorEngine.chat.conversation.current?.conversation.id instead of the flattened
current id; update the access to editorEngine.chat.conversation.current?.id (or
call the store getter getCurrentConversationId()) so it no longer dereferences a
nested conversation property. Also update the other occurrence found in
apps/web/client/src/components/tools/handlers/edit.ts at line ~151 to the same
expression to avoid runtime undefined access.
| import { api } from '@/trpc/react'; | ||
| import { ChatTabContent } from './chat-tab-content'; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add 'use client' at the feature entry boundary.
This component uses client hooks (tRPC React useQuery) and should be the client boundary for the chat feature. Add the directive to avoid RSC/hook runtime errors and keep child observer components server-safe per your guideline.
+'use client';
+
import { api } from '@/trpc/react';
import { ChatTabContent } from './chat-tab-content';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { api } from '@/trpc/react'; | |
| import { ChatTabContent } from './chat-tab-content'; | |
| 'use client'; | |
| import { api } from '@/trpc/react'; | |
| import { ChatTabContent } from './chat-tab-content'; |
🤖 Prompt for AI Agents
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
lines 1-3: this file uses client hooks (tRPC React useQuery) but lacks the React
Server Components client directive, so add "use client" as the very first line
of the file (before any imports) to make this the client boundary for the chat
feature; keep child components that are server-safe unchanged and ensure no
server-only APIs are used in this file after adding the directive.
| const { addToolResult, messages, error, stop, setMessages, regenerate, status } = | ||
| useAiChat<ChatMessage>({ | ||
| id: 'user-chat', | ||
| messages: initialMessages, | ||
| sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, | ||
| transport: new DefaultChatTransport({ | ||
| api: '/api/chat', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Scope the AI-SDK chat id to the conversation.
Using a constant id: 'user-chat' risks state leakage across conversations. Tie it to conversationId for isolation.
- useAiChat<ChatMessage>({
- id: 'user-chat',
+ useAiChat<ChatMessage>({
+ id: conversationId,
messages: initialMessages,
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { addToolResult, messages, error, stop, setMessages, regenerate, status } = | |
| useAiChat<ChatMessage>({ | |
| id: 'user-chat', | |
| messages: initialMessages, | |
| sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, | |
| transport: new DefaultChatTransport({ | |
| api: '/api/chat', | |
| const { addToolResult, messages, error, stop, setMessages, regenerate, status } = | |
| useAiChat<ChatMessage>({ | |
| id: conversationId, | |
| messages: initialMessages, | |
| sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, | |
| transport: new DefaultChatTransport({ |
🤖 Prompt for AI Agents
In apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx around lines
44 to 50, the hard-coded chat id ('user-chat') can cause state to leak between
conversations; replace it with a conversation-scoped id (e.g., include
conversationId or similar unique conversation identifier) such as
`user-chat:${conversationId}` (or use a fallback when conversationId is
undefined) so each conversation gets its own ai-sdk chat state; ensure the
variable you use is in scope and that any places that relied on the old constant
still work with the new scoped id.
| this.creatingConversation = true; | ||
| if (this.current?.messages.length === 0 && !this.current?.conversation.title) { | ||
| if (this.current?.messageCount === 0 && !this.current?.title) { | ||
| throw new Error('Current conversation is already empty.'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t throw for the “already empty” guard; early‑return and skip the error toast.
Throwing here triggers the catch/Toast for a non-error state and sets a scary UX message. Use a no‑op return before toggling the creating flag.
Also, avoid hardcoded user‑facing strings in toasts; route through next‑intl or bubble the error to the UI to translate there.
Apply:
- this.creatingConversation = true;
- if (this.current?.messageCount === 0 && !this.current?.title) {
- throw new Error('Current conversation is already empty.');
- }
+ if (this.current?.messageCount === 0 && !this.current?.title) {
+ return;
+ }
+ this.creatingConversation = true;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.creatingConversation = true; | |
| if (this.current?.messages.length === 0 && !this.current?.conversation.title) { | |
| if (this.current?.messageCount === 0 && !this.current?.title) { | |
| throw new Error('Current conversation is already empty.'); | |
| } | |
| if (this.current?.messageCount === 0 && !this.current?.title) { | |
| return; | |
| } | |
| this.creatingConversation = true; |
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/chat/conversation.ts around lines
55-58, remove the thrown Error for the "already empty" guard and instead perform
an early no-op return (ensuring you return before setting or leaving
creatingConversation true) so the flow skips the catch/toast path; also stop
using a hardcoded user-facing string here — either return a domain signal/error
object to the caller so the UI can translate/display it via next-intl, or call
the i18n translate function before showing any toast in the UI layer.
| void this.deleteConversationInStorage(id); | ||
| if (this.current?.id === id) { | ||
| if (this.conversations.length > 0 && !!this.conversations[0]) { | ||
| this.selectConversation(this.conversations[0].id); | ||
| void this.selectConversation(this.conversations[0].id); | ||
| } else { | ||
| this.startNewConversation(); | ||
| void this.startNewConversation(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Avoid fire‑and‑forget Promises; await deletion/selection to prevent races and unhandled rejections.
Make this method async, await storage deletion, and then await the follow‑up selection/start. Handle failures.
Apply:
- deleteConversation(id: string) {
+ async deleteConversation(id: string) {
if (!this.current) {
console.error('No conversation found');
return;
}
@@
- this.conversations.splice(index, 1);
- void this.deleteConversationInStorage(id);
- if (this.current?.id === id) {
- if (this.conversations.length > 0 && !!this.conversations[0]) {
- void this.selectConversation(this.conversations[0].id);
- } else {
- void this.startNewConversation();
- }
- }
+ this.conversations.splice(index, 1);
+ try {
+ await this.deleteConversationInStorage(id);
+ } catch (error) {
+ console.error('Error deleting conversation', error);
+ // Optionally: rollback splice here if desired.
+ return;
+ }
+ if (this.current && this.current.id === id) {
+ if (this.conversations.length > 0 && this.conversations[0]) {
+ await this.selectConversation(this.conversations[0].id);
+ } else {
+ await this.startNewConversation();
+ }
+ }
}Then verify call sites to ensure they can handle the now‑async API:
🏁 Script executed:
#!/bin/bash
rg -n --type ts --type tsx -C2 '\bdeleteConversation\('Length of output: 27
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "rg version:"
rg --version || true
echo
echo "---- Searching for call sites: deleteConversation( ----"
rg -n -C2 '\bdeleteConversation\s*\(' || true
echo
echo "---- Searching for definition: deleteConversation method ----"
rg -n -C4 '^\s*(?:async\s+)?deleteConversation\s*\(' || true
echo
echo "---- Searching for deleteConversationInStorage occurrences ----"
rg -n -C2 '\bdeleteConversationInStorage\s*\(' || true
echo
echo "---- Show file at apps/web/client/src/components/store/editor/chat/conversation.ts if present ----"
file="apps/web/client/src/components/store/editor/chat/conversation.ts"
if [ -f "$file" ]; then
echo "---- $file (first 300 lines) ----"
sed -n '1,300p' "$file"
else
echo "file not found: $file"
fiLength of output: 7605
Make deleteConversation async and await deletion/selection; update callers
Convert ConversationManager.deleteConversation to async, await deleteConversationInStorage and the subsequent select/start calls to avoid races and unhandled rejections. Apply fix and update all call sites that currently invoke it without awaiting.
Apply:
- deleteConversation(id: string) {
+ async deleteConversation(id: string) {
if (!this.current) {
console.error('No conversation found');
return;
}
@@
- this.conversations.splice(index, 1);
- void this.deleteConversationInStorage(id);
- if (this.current?.id === id) {
- if (this.conversations.length > 0 && !!this.conversations[0]) {
- void this.selectConversation(this.conversations[0].id);
- } else {
- void this.startNewConversation();
- }
- }
+ this.conversations.splice(index, 1);
+ try {
+ await this.deleteConversationInStorage(id);
+ } catch (error) {
+ console.error('Error deleting conversation', error);
+ // Optionally: rollback splice here if desired.
+ return;
+ }
+ if (this.current && this.current.id === id) {
+ if (this.conversations.length > 0 && this.conversations[0]) {
+ await this.selectConversation(this.conversations[0].id);
+ } else {
+ await this.startNewConversation();
+ }
+ }
}Call sites to update:
- apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx:37 — currently calls editorEngine.chat.conversation.deleteConversation(conversationToDelete); change to await or handle the returned Promise.
- Run: rg -n '\bdeleteConversation\s*(' to find and update any other callers.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void this.deleteConversationInStorage(id); | |
| if (this.current?.id === id) { | |
| if (this.conversations.length > 0 && !!this.conversations[0]) { | |
| this.selectConversation(this.conversations[0].id); | |
| void this.selectConversation(this.conversations[0].id); | |
| } else { | |
| this.startNewConversation(); | |
| void this.startNewConversation(); | |
| } | |
| try { | |
| await this.deleteConversationInStorage(id); | |
| } catch (error) { | |
| console.error('Error deleting conversation', error); | |
| // Optionally: rollback splice here if desired. | |
| return; | |
| } | |
| if (this.current && this.current.id === id) { | |
| if (this.conversations.length > 0 && this.conversations[0]) { | |
| await this.selectConversation(this.conversations[0].id); | |
| } else { | |
| await this.startNewConversation(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/chat/conversation.ts around lines
102–108, change deleteConversation to be async and await the call to
deleteConversationInStorage as well as the subsequent
selectConversation/startNewConversation calls to prevent races and unhandled
rejections; update the method signature to return a Promise and ensure internal
awaits are added. Then update all callers to await or properly handle the
returned Promise: specifically change the call in
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
at line ~37 to await
editorEngine.chat.conversation.deleteConversation(conversationToDelete), and run
rg -n '\bdeleteConversation\s*\(' to find and update any remaining call sites to
use await or .catch handling. Ensure tests/usage still compile after these
changes.
| try { | ||
| if (!availableTools[toolName]) { | ||
| toast.error(`Tool "${toolName}" not available in ask mode`, { | ||
| description: `Switch to build mode to use this tool.`, | ||
| duration: 2000, | ||
| }); | ||
|
|
||
| throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`); | ||
| } | ||
| throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
Fix hardcoded/misleading toast text and internationalize
Toast hardcodes “ask mode” and suggests “build mode” regardless of actual mode; also violates the “no hardcoded user-facing text” rule for this path.
Minimal fix (also prepares for i18n placeholders):
- toast.error(`Tool "${toolName}" not available in ask mode`, {
- description: `Switch to build mode to use this tool.`,
+ const targetModeLabel = currentChatMode === ChatType.ASK ? 'build' : 'ask';
+ toast.error(`Tool "${toolName}" not available in ${String(currentChatMode).toLowerCase()} mode`, {
+ description: `Switch to ${targetModeLabel} mode to use this tool.`,
duration: 2000,
});Add import (outside this hunk):
- FUZZY_EDIT_FILE_TOOL_PARAMETERS, getToolSetFromType, GLOB_TOOL_NAME,
+ FUZZY_EDIT_FILE_TOOL_PARAMETERS, getToolSetFromType, ChatType, GLOB_TOOL_NAME,Follow-up: wire next-intl (e.g., pass a t function from caller and replace both strings with message keys).
Verify ChatType is exported from @onlook/ai:
🏁 Script executed:
#!/bin/bash
# Look for ChatType export in the monorepo
rg -nP -C2 'export\s+(?:enum|const|type)\s+ChatType\b' packages/aiLength of output: 0
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "--- rg: find any ChatType symbol ---"
rg -n --hidden -S '\bChatType\b' || true
echo "--- rg: find export definitions for ChatType ---"
rg -nP -C2 'export\s+(?:enum|type|const)\s+ChatType\b' || true
echo "--- rg: search packages/ and apps/ for ChatType ---"
rg -n --hidden -S '\bChatType\b' packages || true
rg -n --hidden -S '\bChatType\b' apps || true
echo "--- Show expected tools.ts if present ---"
if [ -f "apps/web/client/src/components/tools/tools.ts" ]; then
sed -n '1,260p' apps/web/client/src/components/tools/tools.ts || true
else
echo "File not found at apps/web/client/src/components/tools/tools.ts"
echo "Listing apps/web/client/src/components (if exists):"
ls -la apps/web/client/src/components || true
fiLength of output: 22747
Fix hardcoded/misleading toast text and prepare for i18n
File: apps/web/client/src/components/tools/tools.ts (around lines 192–200)
- Replace the hardcoded “ask/build” toast with a dynamic message using ChatType and add the correct import from @onlook/models (do not import ChatType from @onlook/ai). Minimal patch:
- toast.error(`Tool "${toolName}" not available in ask mode`, {
- description: `Switch to build mode to use this tool.`,
+ const targetModeLabel = currentChatMode === ChatType.ASK ? 'Build' : 'Ask';
+ toast.error(`Tool "${toolName}" not available in ${String(currentChatMode).toLowerCase()} mode`, {
+ description: `Switch to ${targetModeLabel} mode to use this tool.`,
duration: 2000,
});Add import (top of file):
-} from '@onlook/ai';
+} from '@onlook/ai';
+import { ChatType } from '@onlook/models';- Follow-up: replace these strings with next-intl message keys (or reuse the existing ChatType→label mapping used in chat-mode-toggle) to comply with the “no hardcoded user-facing text” rule.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| if (!availableTools[toolName]) { | |
| toast.error(`Tool "${toolName}" not available in ask mode`, { | |
| description: `Switch to build mode to use this tool.`, | |
| duration: 2000, | |
| }); | |
| throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`); | |
| } | |
| throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`); | |
| } | |
| } from '@onlook/ai'; | |
| import { ChatType } from '@onlook/models'; | |
| try { | |
| if (!availableTools[toolName]) { | |
| const targetModeLabel = currentChatMode === ChatType.ASK ? 'Build' : 'Ask'; | |
| toast.error(`Tool "${toolName}" not available in ${String(currentChatMode).toLowerCase()} mode`, { | |
| description: `Switch to ${targetModeLabel} mode to use this tool.`, | |
| duration: 2000, | |
| }); | |
| throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`); | |
| } |
🤖 Prompt for AI Agents
In apps/web/client/src/components/tools/tools.ts around lines 192–200, the toast
message uses hardcoded "ask/build" text which is misleading and not i18n-ready;
import ChatType from @onlook/models at the top of the file, compute the current
mode label dynamically using ChatType (or reuse the existing ChatType→label
mapping from chat-mode-toggle), and replace the hardcoded strings in the toast
error and thrown Error with that dynamic label; afterwards switch these
user-facing strings to next-intl message keys (or reuse existing labels) to
remove hardcoded text and prepare for localization.
| .input(z.object({ | ||
| conversationId: z.string(), | ||
| messages: messageInsertSchema.array(), | ||
| })) | ||
| .mutation(async ({ ctx, input }) => { | ||
| await ctx.db.transaction(async (tx) => { | ||
| await tx.delete(messages).where(eq(messages.conversationId, input.conversationId)); | ||
|
|
||
| if (input.messages.length > 0) { | ||
| const normalizedMessages = input.messages.map(normalizeMessage); | ||
| await tx.insert(messages).values(normalizedMessages); | ||
| } | ||
|
|
||
| await tx.update(conversations).set({ | ||
| updatedAt: new Date() | ||
| }).where(eq(conversations.id, input.conversationId)); | ||
| }); | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforce conversationId on insert and guard payload size.
Prevent accidental/malicious cross-conversation writes by overriding each message’s conversationId with input.conversationId. Also cap batch size to avoid large transactions.
- replaceConversationMessages: protectedProcedure
- .input(z.object({
- conversationId: z.string(),
- messages: messageInsertSchema.array(),
- }))
+ replaceConversationMessages: protectedProcedure
+ .input(z.object({
+ conversationId: z.string(),
+ messages: messageInsertSchema.array().max(2000), // safety cap
+ }))
.mutation(async ({ ctx, input }) => {
await ctx.db.transaction(async (tx) => {
await tx.delete(messages).where(eq(messages.conversationId, input.conversationId));
if (input.messages.length > 0) {
- const normalizedMessages = input.messages.map(normalizeMessage);
+ const normalizedMessages = input.messages.map((m) =>
+ normalizeMessage({ ...m, conversationId: input.conversationId }),
+ );
await tx.insert(messages).values(normalizedMessages);
}
await tx.update(conversations).set({
updatedAt: new Date()
}).where(eq(conversations.id, input.conversationId));
});
}),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .input(z.object({ | |
| conversationId: z.string(), | |
| messages: messageInsertSchema.array(), | |
| })) | |
| .mutation(async ({ ctx, input }) => { | |
| await ctx.db.transaction(async (tx) => { | |
| await tx.delete(messages).where(eq(messages.conversationId, input.conversationId)); | |
| if (input.messages.length > 0) { | |
| const normalizedMessages = input.messages.map(normalizeMessage); | |
| await tx.insert(messages).values(normalizedMessages); | |
| } | |
| await tx.update(conversations).set({ | |
| updatedAt: new Date() | |
| }).where(eq(conversations.id, input.conversationId)); | |
| }); | |
| }), | |
| .input(z.object({ | |
| conversationId: z.string(), | |
| messages: messageInsertSchema.array().max(2000), // safety cap | |
| })) | |
| .mutation(async ({ ctx, input }) => { | |
| await ctx.db.transaction(async (tx) => { | |
| await tx.delete(messages).where(eq(messages.conversationId, input.conversationId)); | |
| if (input.messages.length > 0) { | |
| const normalizedMessages = input.messages.map((m) => | |
| normalizeMessage({ ...m, conversationId: input.conversationId }), | |
| ); | |
| await tx.insert(messages).values(normalizedMessages); | |
| } | |
| await tx.update(conversations).set({ | |
| updatedAt: new Date() | |
| }).where(eq(conversations.id, input.conversationId)); | |
| }); | |
| }), |
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/routers/chat/message.ts around lines 96 to
113, ensure we cannot insert messages into a different conversation and prevent
huge batches: before the transaction validate input.messages length (e.g. reject
or throw if messages.length > MAX_BATCH_SIZE, choose a sensible constant like
100), and when building normalizedMessages override each message.conversationId
= input.conversationId (or map to a new object that sets conversationId to
input.conversationId) so the DB insert always uses the provided conversationId;
perform these checks and normalization before calling ctx.db.transaction so the
transaction only runs with a capped, safe payload whose conversationId values
are enforced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5)
28-41: Guard streaming when the array is empty.If
isStreamingis true andbaseMessages.length === 0,baseMessages[baseMessages.length - 1]isundefined. Add a length check.Apply:
- const { messages, streamedMessage } = useMemo(() => { - console.log('baseMessages', baseMessages.length, isStreaming); - if (isStreaming) { - return { - messages: baseMessages.slice(0, -1), - streamedMessage: baseMessages[baseMessages.length - 1], - }; - } - return { - messages: baseMessages, - streamedMessage: null, - }; - }, [baseMessages, isStreaming]); + const { messages, streamedMessage } = useMemo(() => { + if (isStreaming && baseMessages.length > 0) { + return { + messages: baseMessages.slice(0, -1), + streamedMessage: baseMessages[baseMessages.length - 1], + }; + } + return { + messages: baseMessages, + streamedMessage: null, + }; + }, [baseMessages, isStreaming]);
29-30: Remove debug logs or gate behind a config flag.Console logging in render paths adds noise and small perf overhead; could leak message content in shared environments.
Apply:
- console.log('baseMessages', baseMessages.length, isStreaming); ... - console.log('messages', messages.length, streamedMessage);If needed, guard via an app-level debug flag from
@/env.Also applies to: 42-42
44-68: Keys: avoid duplicates and index; skip empty wrappers.
- Don’t pass
keyto inner components; key the wrapper only.- Avoid using
indexin the key.- Skip rendering the wrapper when the role is
system.Apply:
- const renderMessage = useCallback( - (message: ChatMessage, index: number) => { - let messageNode; + const renderMessage = useCallback( + (message: ChatMessage) => { + let messageNode; switch (message.role) { case 'assistant': - messageNode = <AssistantMessage key={message.id} message={message} />; + messageNode = <AssistantMessage message={message} />; break; case 'user': - messageNode = ( - <UserMessage - key={message.id} - onEditMessage={onEditMessage} - message={message} - /> - ); + messageNode = <UserMessage onEditMessage={onEditMessage} message={message} />; break; case 'system': - messageNode = null; - break; + return null; default: assertNever(message.role); } - return <div key={`message-${message.id}-${index}`}>{messageNode}</div>; + return <div key={`message-${message.id}`}>{messageNode}</div>; }, [onEditMessage], ); ... - {messages.map((message, index) => renderMessage(message, index))} + {messages.map(renderMessage)}Also applies to: 88-88
85-91: contentKey should include the streamed message id (not the last non‑stream id).When streaming,
messagesexcludes the last element; usestreamedMessage?.idto keep the key stable/accurate.Apply:
- <ChatMessageList - contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`} - > + <ChatMessageList + contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming && streamedMessage ? `|${streamedMessage.id}` : ''}`} + >
71-82: Returnnullexplicitly when hiding the empty state.Current
return (!selected.length && <div/>)returnsfalse; returnnullfor clarity.Apply:
- if (!messages || messages.length === 0) { - return ( - !editorEngine.elements.selected.length && ( - <div className="flex-1 flex flex-col items-center justify-center text-foreground-tertiary/80 h-full"> - <Icons.EmptyState className="size-32" /> - <p className="text-center text-regularPlus text-balance max-w=[300px]"> - {t(transKeys.editor.panels.edit.tabs.chat.emptyState)} - </p> - </div> - ) - ); - } + if (!messages || messages.length === 0) { + if (editorEngine.elements.selected.length) return null; + return ( + <div className="flex-1 flex flex-col items-center justify-center text-foreground-tertiary/80 h-full"> + <Icons.EmptyState className="size-32" /> + <p className="text-center text-regularPlus text-balance max-w-[300px]"> + {t(transKeys.editor.panels.edit.tabs.chat.emptyState)} + </p> + </div> + ); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env insteadAvoid hardcoded user-facing text; use next-intl messages/hooks
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks insteadUse path aliases @/* and ~/* for imports mapping to src/*
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use the any type unless necessary
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using the any type unless absolutely necessary
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
EditMessage(19-23)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
UserMessage(29-264)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
StreamMessage(6-27)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
ErrorMessage(11-63)
🔇 Additional comments (3)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (3)
1-1: Type‑only import: good.Using
import typekeeps bundles lean and avoids runtime deps.
16-21: Props shape looks solid.Clear, minimal API with explicit
error?: Error.
23-25: Client boundary present at right-panel — no change required.apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx contains "use client" at the module top and therefore provides the single client boundary covering chat-tab/chat-messages; chat-tab-content/index.tsx does not include "use client" and does not need one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (4)
79-87: Double-invoking suggestion selection on Enter (triggers side effects twice).Call
handleEnterSelectiononce and gate handling on its boolean result.Apply:
- const handleGlobalKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter' && suggestionRef.current?.handleEnterSelection()) { - e.preventDefault(); - e.stopPropagation(); - // Stop the event from bubbling to the canvas - e.stopImmediatePropagation(); - // Handle the suggestion selection - suggestionRef.current.handleEnterSelection(); - } - }; + const handleGlobalKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + const handled = suggestionRef.current?.handleEnterSelection?.() === true; + if (handled) { + e.preventDefault(); + e.stopPropagation(); + // Stop the event from bubbling to the canvas + e.stopImmediatePropagation(); + } + } + };
147-150: Localize user-facing error toast.Avoid hardcoded text; use next-intl.
Apply:
- toast.error('Failed to send message. Please try again.'); + toast.error(t(transKeys.editor.panels.edit.tabs.chat.input.errors.sendFailed));
153-158: Localize ASK-mode placeholder.Replace hardcoded string with a translation key.
Apply:
- if (chatMode === ChatType.ASK) { - return 'Ask a question about your project...'; - } + if (chatMode === ChatType.ASK) { + return t(transKeys.editor.panels.edit.tabs.chat.input.askPlaceholder); + }
232-271: Localize screenshot messages and label.All toasts and labels should use translations.
Apply:
- toast.error('No active frame available for screenshot'); + toast.error(t(transKeys.editor.panels.edit.tabs.chat.screenshot.noActiveFrame));- toast.error('Failed to capture screenshot. Please refresh the page and try again.'); + toast.error(t(transKeys.editor.panels.edit.tabs.chat.screenshot.captureFailedWithRefresh));- displayName: 'Screenshot', + displayName: t(transKeys.editor.panels.edit.tabs.chat.screenshot.label),- toast.success('Screenshot added to chat'); + toast.success(t(transKeys.editor.panels.edit.tabs.chat.screenshot.added));- toast.error('Failed to capture screenshot. Please try again.'); + toast.error(t(transKeys.editor.panels.edit.tabs.chat.screenshot.captureFailed));
🧹 Nitpick comments (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (4)
47-48: StaleisStreamingcaptured in window listener.The handler closes over the initial value; use a ref to read the current state.
Apply:
const chatMode = editorEngine.state.chatMode; - const [inputValue, setInputValue] = useState(''); + const [inputValue, setInputValue] = useState(''); + const isStreamingRef = useRef(isStreaming); + + useEffect(() => { + isStreamingRef.current = isStreaming; + }, [isStreaming]);- const focusHandler = () => { - if (textareaRef.current && !isStreaming) { + const focusHandler = () => { + if (textareaRef.current && !isStreamingRef.current) { focusInput(); } };Also applies to: 68-71
408-418: Add accessible labels to icon-only buttons and localize tooltip text.Improve a11y and i18n.
Apply:
- <Button + <Button + aria-label={t(transKeys.editor.panels.edit.tabs.chat.input.stopResponse)} size={'icon'} variant={'secondary'} className="text-smallPlus w-fit h-full py-0.5 px-2.5 text-primary" onClick={() => { setActionTooltipOpen(false); void onStop(); }} >- <TooltipContent>{'Stop response'}</TooltipContent> + <TooltipContent>{t(transKeys.editor.panels.edit.tabs.chat.input.stopResponse)}</TooltipContent>- <Button + <Button + aria-label={t(transKeys.editor.panels.edit.tabs.chat.input.send)} size={'icon'} variant={'secondary'} className="text-smallPlus w-fit h-full py-0.5 px-2.5 text-primary" disabled={inputEmpty || disabled} onClick={() => void sendMessage()} >Also applies to: 420-420, 423-432
295-305: DragEvent DataTransfer may not be constructible across browsers. Add a fallback.Safari/Firefox historically restrict setting
dataTransferin constructed events.Apply:
- e.currentTarget.parentElement?.dispatchEvent( - new DragEvent(eventType, { - bubbles: true, - cancelable: true, - dataTransfer: e.dataTransfer, - }), - ); + const parent = e.currentTarget.parentElement; + if (!parent) return; + try { + parent.dispatchEvent( + new DragEvent(eventType, { + bubbles: true, + cancelable: true, + dataTransfer: e.dataTransfer, + }), + ); + } catch { + // Fallback for browsers that disallow setting dataTransfer + parent.dispatchEvent(new Event(eventType, { bubbles: true, cancelable: true })); + }
193-201: DRY up image-limit checks.Factor the repeated limit-check/toast into a helper for consistency.
You can add:
function ensureImageSlotAvailable(limit = 1): boolean { const currentImages = editorEngine.chat.context.context.filter( (ctx) => ctx.type === MessageContextType.IMAGE ); const { success, errorMessage } = validateImageLimit(currentImages, limit); if (!success) toast.error(errorMessage); return success; }Then:
- In
handleImageEvent:if (!ensureImageSlotAvailable(1)) return;- In
handleScreenshot: same guard.Also applies to: 218-228
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/client/src/app/api/chat/route.ts(2 hunks)apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx(14 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx(1 hunks)apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
- apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
- apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
- apps/web/client/src/app/api/chat/route.ts
- apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env insteadAvoid hardcoded user-facing text; use next-intl messages/hooks
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
apps/web/client/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks insteadUse path aliases @/* and ~/* for imports mapping to src/*
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
apps/web/client/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use the any type unless necessary
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
apps/web/client/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using the any type unless absolutely necessary
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
SendMessage(18-18)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)apps/web/client/src/components/store/editor/chat/index.ts (1)
sendMessage(43-49)
| @@ -1,8 +1,9 @@ | |||
|
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Must mark as Client Component (uses hooks, events, observer).
This file runs client-only code; without a client boundary it will fail in Next.js.
Apply at top:
+ 'use client';🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Looking for the target file (exact path)..."
target="apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx"
if [ -f "$target" ]; then
echo "FOUND: $target"
files=("$target")
else
echo "Not found at exact path. Searching for 'chat-input/index.tsx' under apps/web/client/src/app..."
mapfile -t files < <(rg --hidden --files apps/web/client/src/app 2>/dev/null | rg 'chat-input/index.tsx' || true)
if [ ${#files[@]} -eq 0 ]; then
echo "No matching files found. Listing top-level tree for apps/web/client/src/app/project (maxdepth 4) to help locate file..."
if [ -d "apps/web/client/src/app/project" ]; then
find apps/web/client/src/app/project -maxdepth 4 -type f | sed -n '1,200p'
else
echo "Directory apps/web/client/src/app/project does not exist."
fi
exit 0
fi
fi
for file in "${files[@]}"; do
echo
echo "===== FILE: $file ====="
echo "---- head (1..200) ----"
sed -n '1,200p' "$file" || true
echo
echo "---- 'use client' directive (exact match) ----"
rg -n "^['\"]use client['\"]" "$file" || echo "(none)"
echo
echo "---- client-only hooks / events / observer usage ----"
rg -n -C2 --hidden -S '\b(useState|useEffect|useRef|useCallback|useMemo|useContext|useTransition|onClick|onSubmit|onChange|onBlur|observer)\b' "$file" || echo "(none found)"
echo
echo "---- imports referencing observer/mobx/react hooks ----"
rg -n "import .*observer|from (\"|')mobx|mobx-react|mobx-react-lite|react-use|@hookstate" "$file" || echo "(none found)"
echo "=============================="
done
echo
echo "---- Files under apps/web/client/src/app that include a top-level 'use client' directive ----"
rg -n --hidden "^['\"]use client['\"]" apps/web/client/src/app || echo "(none)"Length of output: 20299
Add 'use client' at the top of this file (component uses React hooks, mobx observer, and browser/DOM events).
File: apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
+ 'use client';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'use client'; |
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
around lines 1 to 1, this component uses React hooks, mobx observer, and
browser/DOM events but is missing the required React Server Components
directive; add the line 'use client' as the very first statement in the file
(above all imports) so the component is executed on the client, then run a quick
build/type-check to ensure no other client-only usages are flagged.
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
Show resolved
Hide resolved
| void addToolResult({ | ||
| tool: toolName, | ||
| toolCallId: toolCall.toolCallId, | ||
| output: output, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect async handling - addToolResult is called with void but the function signature shows it returns Promise. This should be awaited to ensure proper error handling and completion of the tool result addition.
| void addToolResult({ | |
| tool: toolName, | |
| toolCallId: toolCall.toolCallId, | |
| output: output, | |
| }); | |
| await addToolResult({ | |
| tool: toolName, | |
| toolCallId: toolCall.toolCallId, | |
| output: output, | |
| }); |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/web/client/src/components/tools/handlers/edit.ts (2)
22-37: Guard against empty search strings to prevent pathological replaces.If old_string is empty, replace/replaceAll can explode content. Add a validation before applying edits.
let newContent: string; + if (!args.old_string) { + throw new Error('old_string cannot be empty'); + } if (args.replace_all) { newContent = file.content.replaceAll(args.old_string, args.new_string); } else {
67-84: Validate each edit: non-empty old_string and non-empty edits array.Preempt invalid inputs early and give clearer errors.
- // Validate only the first non-replace_all edit against original content + // Validate only the first non-replace_all edit against original content // Sequential edits will be validated during application let tempContent = originalContent; + if (!args.edits.length) { + throw new Error('No edits provided'); + } for (const edit of args.edits) { + if (!edit.old_string) { + throw new Error('old_string cannot be empty'); + } if (!edit.replace_all) { const firstIndex = tempContent.indexOf(edit.old_string); if (firstIndex === -1) { throw new Error(`String not found in file: ${edit.old_string}`); }apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (1)
15-31: Bug: contextPercentage ignores real usage and hardcodes model window.Use typed cumulativeUsage from the store, not a zeroed stub; avoid stale “Claude” comment, and parameterize max tokens per active model if available.
- const contextPercentage = useMemo(() => { - // Use cumulative usage data if available, otherwise fall back to estimate - const cumulativeUsage = { - promptTokens: 0, - completionTokens: 0, - totalTokens: 0, - }; + const contextPercentage = useMemo(() => { + // Use cumulative usage data if available, otherwise fall back to estimate + const cumulativeUsage = editorEngine.chat.context.cumulativeUsage; @@ - if (cumulativeUsage && cumulativeUsage.totalTokens > 0) { - // Use cumulative token usage from all API responses - // Assume a context window of ~200k tokens for Claude 4 Sonnet - const maxContextTokens = 200000; + if (cumulativeUsage && cumulativeUsage.totalTokens > 0) { + // Use cumulative token usage from all API responses + // TODO: derive maxContextTokens from the active model selection + const maxContextTokens = 200000;Follow-up (cross-file): expose a typed cumulativeUsage on ChatContext to remove any-casts here (see comment in chat/context.ts).
♻️ Duplicate comments (1)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
46-47: Scope chat id to the conversation to prevent state bleed.Use a conversation-scoped id instead of a constant. This isolates ai-sdk state per thread.
- id: 'user-chat', + id: conversationId,
🧹 Nitpick comments (11)
apps/web/client/src/components/tools/handlers/edit.ts (2)
86-104: No-op fast path when content didn’t change.Avoid unnecessary writes when edits result in identical content.
// Apply edits sequentially in the order provided // Each edit operates on the result of the previous edit for (const edit of args.edits) { if (edit.replace_all) { content = content.replaceAll(edit.old_string, edit.new_string); } else { const index = content.indexOf(edit.old_string); if (index === -1) { throw new Error(`String not found in file after previous edits: ${edit.old_string}`); } content = content.replace(edit.old_string, edit.new_string); } } + if (content === originalContent) { + return `No changes applied to ${args.file_path}`; + } + const result = await sandbox.writeFile(args.file_path, content);
45-47: Preserve original error message when rethrowing.Stringifying the error object can yield “[object Object]”. Use message or String(error).
- } catch (error) { - throw new Error(`Cannot edit file ${args.file_path}: ${error}`); - } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Cannot edit file ${args.file_path}: ${message}`); + }apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (3)
112-116: Send the actual chatType to analytics.Don’t hardcode ChatType.EDIT; use the provided chatType.
- posthog.capture('user_edit_message', { type: ChatType.EDIT }); + posthog.capture('user_edit_message', { type: chatType });
139-144: Pass context on edit regenerate for parity with send.Maintain server parity by including context as in sendMessage.
void regenerate({ body: { chatType, conversationId, + context, }, });
115-116: Remove debug log.- console.log('[RERENDER] editMessage');apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (1)
29-30: Remove console log.- console.log('baseMessages', baseMessages.length, isStreaming);apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (4)
47-51: Remove any-casts; use proper type narrowing.- messages.forEach(message => { + messages.forEach((message) => { // Extract text content from message parts - const textContent = message.parts - .filter(part => part.type === 'text') - .map(part => (part as any).text) - .join(' '); + const textContent = message.parts + .map((p) => (p.type === 'text' ? p.text : '')) + .join(' '); @@ - if (message.metadata?.context) { - message.metadata.context.forEach((contextItem: any) => { - if (contextItem.content) { - estimatedTokens += Math.ceil(contextItem.content.length / 4); - } - }); - } + message.metadata?.context?.forEach((contextItem) => { + if ('content' in contextItem && typeof contextItem.content === 'string') { + estimatedTokens += Math.ceil(contextItem.content.length / 4); + } + });Also applies to: 57-63
71-71: Avoid any in deps; depend on a typed field.Swap (editorEngine.chat.context as any).cumulativeUsage for a typed property.
- }, [(editorEngine.chat.context as any).cumulativeUsage, editorEngine.chat.context.context]); + }, [editorEngine.chat.context.cumulativeUsage, editorEngine.chat.context.context]);
33-34: Messages are hardcoded to an empty array — token estimates and tooltip counts are wrong.Inject actual messages (e.g., via props) or source them from a typed store to keep estimates accurate.
Would you like a follow-up patch to accept messages?: ChatMessage[] in ContextWheelProps and wire it from ChatTabContent/useChat?
Also applies to: 92-95
137-156: Localize user-facing strings.Wrap strings in next-intl (title/labels/units) per repo guidelines.
I can generate the minimal i18n keys and replacements if you want.
apps/web/client/src/components/store/editor/chat/context.ts (1)
196-215: getErrorContext: consider truncating large payloads.Long error contents can balloon context; limit per-branch bytes or entries to protect prompt budget.
I can add a safe truncation (e.g., first 2–3 errors per branch, max chars per error) if helpful.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx(4 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx(2 hunks)apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx(0 hunks)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx(1 hunks)apps/web/client/src/components/store/editor/chat/context.ts(4 hunks)apps/web/client/src/components/tools/handlers/edit.ts(2 hunks)
💤 Files with no reviewable changes (1)
- apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks insteadUse path aliases @/* and ~/* for imports mapping to src/*
Files:
apps/web/client/src/components/tools/handlers/edit.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/components/store/editor/chat/context.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use the any type unless necessary
Files:
apps/web/client/src/components/tools/handlers/edit.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/components/store/editor/chat/context.ts
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using the any type unless absolutely necessary
Files:
apps/web/client/src/components/tools/handlers/edit.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env insteadAvoid hardcoded user-facing text; use next-intl messages/hooks
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
apps/web/client/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
apps/web/client/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
🧬 Code graph analysis (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (2)
packages/db/src/schema/chat/message.ts (1)
messages(11-27)packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (9)
packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
EditMessage(19-23)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/assistant-message.tsx (1)
AssistantMessage(4-15)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
UserMessage(29-264)apps/web/client/src/i18n/keys.ts (1)
transKeys(5-5)packages/ui/src/components/chat/chat-message-list.tsx (1)
ChatMessageList(53-53)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
StreamMessage(6-27)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
ErrorMessage(11-63)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (7)
packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/components/tools/tools.ts (1)
handleToolCall(186-218)apps/web/client/src/components/store/editor/chat/index.ts (1)
sendMessage(43-49)apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (3)
getUserChatMessageFromString(18-35)prepareMessagesForSuggestions(6-16)attachCommitToUserMessage(38-68)apps/web/client/src/trpc/react.tsx (1)
api(23-23)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (2)
MessageContext(53-59)ErrorMessageContext(43-46)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)
🔇 Additional comments (2)
apps/web/client/src/components/tools/handlers/edit.ts (1)
149-153: Verify conversationId presence in metadata.If editorEngine.chat.conversation.current?.id can be undefined, api.code.applyDiff may lose association. Either require a conversation or omit the field when absent.
Would you like a follow-up patch that enforces a non-null conversationId (with a user-visible warning) before calling applyDiff?
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
176-189: Ensure Array.prototype.findLast is supported at runtime or avoid using it
- findLast is an ES2023 runtime API; TypeScript "lib" only provides typings — the runtime (browsers/Node) must support it or you must polyfill/replace it.
- Occurrences: apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx:176, apps/web/client/src/app/api/chat/route.ts:74.
- Action: confirm the extended config "@onlook/typescript/next-react.json" (used by apps/web/client/tsconfig.json) exposes "lib": ["ES2023"|"ESNext"] for typings and that your production runtimes include findLast; otherwise add a runtime polyfill (e.g., core-js) or replace findLast with a compatible pattern (e.g., reverse+find).
| contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix contentKey: include streamedMessage.id, not the last non-streaming id.
Current key won’t change with streaming updates, breaking scroll/transition behavior.
- <ChatMessageList
- contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`}
+ <ChatMessageList
+ contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${streamedMessage?.id ?? ''}` : ''}`}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`} | |
| > | |
| <ChatMessageList | |
| contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${streamedMessage?.id ?? ''}` : ''}`} | |
| > |
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
around lines 84-85, the contentKey currently appends the last message id when
isStreaming is true which doesn't change as the streamed text updates; change
the key to include the streaming message's id (streamedMessage?.id) instead of
messages?.[messages.length - 1]?.id so the key updates with streaming progress.
Update the template to use streamedMessage?.id when isStreaming (with a safe
fallback to ''), ensuring proper optional chaining and preserving the rest of
the messages map in the key.
| onToolCall: async (toolCall) => { | ||
| setIsExecutingToolCall(true); | ||
| void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => { | ||
| setIsExecutingToolCall(false); | ||
| }); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Await tool-call handling and always clear isExecutingToolCall.
Current .then(...) can flip the flag before addToolResult finishes, misreporting streaming state. Use try/finally and await the handler.
- onToolCall: async (toolCall) => {
- setIsExecutingToolCall(true);
- void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => {
- setIsExecutingToolCall(false);
- });
- },
+ onToolCall: async (toolCall) => {
+ setIsExecutingToolCall(true);
+ try {
+ await handleToolCall(toolCall.toolCall, editorEngine, addToolResult);
+ } finally {
+ setIsExecutingToolCall(false);
+ }
+ },Additionally, update handleToolCall to await addToolResult so the promise resolves after the result is recorded:
// apps/web/client/src/components/tools/tools.ts (cross-file)
export async function handleToolCall(/*...*/) {
// ...
try {
// ...
output = await clientTool.handler(toolCall.input, editorEngine);
} catch (error) {
output = 'error handling tool call ' + error;
} finally {
- void addToolResult({ tool: toolName, toolCallId: toolCall.toolCallId, output });
+ await addToolResult({ tool: toolName, toolCallId: toolCall.toolCallId, output });
}
}🤖 Prompt for AI Agents
In apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx around lines
56-61, the onToolCall handler sets isExecutingToolCall and uses .then(...) which
can clear the flag before addToolResult finishes; change this to await the call
inside a try/finally so setIsExecutingToolCall(false) always runs after the
handler completes. Also update the cross-file function handleToolCall
(apps/web/client/src/components/tools/tools.ts) so its finally block awaits
addToolResult (i.e., make addToolResult return a promise and await it) so the
handler’s returned promise only resolves after the result is recorded.
| export class ChatContext { | ||
| context: MessageContext[] = []; | ||
| private selectedReactionDisposer?: () => void; | ||
| constructor( | ||
| private editorEngine: EditorEngine, | ||
| ) { | ||
|
|
||
| constructor(private editorEngine: EditorEngine) { | ||
| makeAutoObservable(this); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Expose typed cumulativeUsage on ChatContext.
This removes any-casts in ContextWheel and enables accurate usage-based percentages.
export class ChatContext {
context: MessageContext[] = [];
+ // Aggregated token usage across the active conversation (client-updated)
+ cumulativeUsage: { promptTokens: number; completionTokens: number; totalTokens: number } | null = null;
@@
constructor(private editorEngine: EditorEngine) {
makeAutoObservable(this);
}Optionally add a setter to keep updates observable:
setCumulativeUsage(usage: { promptTokens: number; completionTokens: number; totalTokens: number } | null) {
this.cumulativeUsage = usage;
}🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/chat/context.ts around lines 16
to 23, add a strongly-typed cumulativeUsage property to ChatContext (type: {
promptTokens: number; completionTokens: number; totalTokens: number } | null)
instead of relying on any casts elsewhere, initialize it to null, and ensure it
remains observable by keeping makeAutoObservable(this) in the constructor;
optionally add a setCumulativeUsage(usage: { promptTokens: number;
completionTokens: number; totalTokens: number } | null) method that assigns
this.cumulativeUsage = usage so updates are tracked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/client/src/components/store/editor/chat/context.ts (1)
24-29: Prevent stale context writes from async reaction (race on rapid selection).If selection changes quickly, earlier getChatContext() results can overwrite newer context. Gate with a version token.
- init() { - this.selectedReactionDisposer = reaction( - () => this.editorEngine.elements.selected, - () => this.getChatContext().then((context) => (this.context = context)), - ); - } + init() { + this.selectedReactionDisposer = reaction( + () => this.editorEngine.elements.selected, + () => { + const version = ++this.selectionVersion; + void this.getChatContext().then((context) => { + if (version === this.selectionVersion) { + this.context = context; + } + }); + }, + ); + }Add the backing field (outside the shown range):
// class ChatContext { ... private selectionVersion = 0;apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)
79-88: Fix double invocation of suggestion selection on Enter.
handleEnterSelection()is called twice (in the condition and again inside), causing duplicate handling.- const handleGlobalKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter' && suggestionRef.current?.handleEnterSelection()) { + const handleGlobalKeyDown = (e: KeyboardEvent) => { + const handled = e.key === 'Enter' && !!suggestionRef.current?.handleEnterSelection(); + if (handled) { e.preventDefault(); e.stopPropagation(); // Stop the event from bubbling to the canvas e.stopImmediatePropagation(); - // Handle the suggestion selection - suggestionRef.current.handleEnterSelection(); } };
♻️ Duplicate comments (6)
apps/web/client/src/components/store/editor/chat/conversation.ts (2)
90-110: Make deleteConversation async; await storage ops and follow‑ups.Fire‑and‑forget promises risk races/unhandled rejections when switching conversations.
- deleteConversation(id: string) { + async deleteConversation(id: string) { if (!this.current) { console.error('No conversation found'); return; } @@ this.conversations.splice(index, 1); - void this.deleteConversationInStorage(id); - if (this.current?.id === id) { - if (this.conversations.length > 0 && !!this.conversations[0]) { - void this.selectConversation(this.conversations[0].id); - } else { - void this.startNewConversation(); - } - } + try { + await this.deleteConversationInStorage(id); + } catch (error) { + console.error('Error deleting conversation', error); + return; + } + if (this.current && this.current.id === id) { + if (this.conversations.length > 0 && this.conversations[0]) { + await this.selectConversation(this.conversations[0].id); + } else { + await this.startNewConversation(); + } + } }#!/bin/bash # Find call sites that must now await deleteConversation rg -n -C2 '\bdeleteConversation\s*\(' --type ts --type tsx
55-57: Don’t throw for “already empty” guard; early-return before toggling the flag.Throwing here triggers catch/toast for a non-error state and briefly flips
creatingConversation. Return no-op instead.- this.creatingConversation = true; - if (this.current?.messageCount === 0 && !this.current?.title) { - throw new Error('Current conversation is already empty.'); - } + if (this.current?.messageCount === 0 && !this.current?.title) { + return; + } + this.creatingConversation = true;apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (2)
170-172: Localize pasted/dropped image labels.Use next-intl; avoid hardcoded user-facing text.
- void handleImageEvent(file, 'Pasted image'); + void handleImageEvent(file, t(transKeys.editor.panels.edit.tabs.chat.input.image.pasted));- void handleImageEvent(file, 'Dropped image'); + void handleImageEvent(file, t(transKeys.editor.panels.edit.tabs.chat.input.image.dropped));Also applies to: 187-188
1-1: Add 'use client'.This component uses hooks/events and must be a Client Component in Next.js.
+'use client'; +apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (2)
46-48: Scope chatidto the conversation to prevent cross‑chat state leakage.Using a constant id merges streams across tabs/conversations.
- id: 'user-chat', + id: conversationId,
56-61: Await tool‑call handling; always clearisExecutingToolCallinfinally.Current
.then(...)can flip the flag early and race withaddToolResult.- onToolCall: async (toolCall) => { - setIsExecutingToolCall(true); - void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => { - setIsExecutingToolCall(false); - }); - }, + onToolCall: async (toolCall) => { + setIsExecutingToolCall(true); + try { + await handleToolCall(toolCall.toolCall, editorEngine, addToolResult); + } finally { + setIsExecutingToolCall(false); + } + },Also update the tool handler to await persistence:
--- a/apps/web/client/src/components/tools/tools.ts +++ b/apps/web/client/src/components/tools/tools.ts @@ - } finally { - void addToolResult({ + } finally { + await addToolResult({ tool: toolName, toolCallId: toolCall.toolCallId, output: output, }); }
🧹 Nitpick comments (21)
packages/ai/src/prompt/ask.ts (1)
33-33: Nit: tighten wording (“code changes”).Minor copy edit for clarity.
-Remember: You're a thoughtful advisor helping users make informed decisions about their project, not an automated coding assistant. If the user asks for code change, you can prompt them to toggle to EDIT MODE to make the changes. +Remember: You're a thoughtful advisor helping users make informed decisions about their project, not an automated coding assistant. If the user asks for code changes, prompt them to toggle to EDIT MODE to make the changes.apps/web/client/src/components/store/editor/chat/context.ts (3)
221-228: Rename shadowed variable for readability.Inner
branchErrorsshadows the outer constant. Rename to avoid confusion.- return Array.from(branchGroups.entries()).map(([branchId, branchErrors]) => ({ + return Array.from(branchGroups.entries()).map(([branchId, errors]) => ({ type: MessageContextType.ERROR, - content: branchErrors + content: errors .map((e) => `Source: ${e.sourceId}\nContent: ${e.content}\n`) .join('\n'), - displayName: `Errors - ${branchErrors[0]?.branchName || 'Unknown Branch'}`, + displayName: `Errors - ${errors[0]?.branchName || 'Unknown Branch'}`, branchId, }));
210-229: Localize or defer user-visible labels.
displayNamestrings (e.g., “Errors - …”, “Unknown Branch”) can surface in the UI (context pills). Move label formatting to the UI layer with next-intl, or inject translated strings via parameters.Please confirm whether
displayNamefrom context surfaces directly to users. If yes, I can propose a small adapter to map raw context → localized labels at render sites.
104-109: Unnecessary async.
getImageContextis synchronous. Consider removingasyncto simplify call sites, or keep as-is if you anticipate I/O later.apps/web/client/src/components/store/editor/chat/conversation.ts (2)
56-66: Emptiness check may be unreliable (messageCount reset on select).
selectConversationsetsmessageCount: 0, so this guard can block starting a new conversation right after selecting an existing one. Prefer a separatehasMessagesflag derived from server or remove the guard.Confirm where
setConversationLength()is called post-select to reflect the true count; otherwise this guard should be removed.
68-71: Localize toast text.Avoid hardcoded user-facing strings in apps/web/client/src/**/*.{ts,tsx}. Route through next-intl or bubble errors to the UI.
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)
108-118: Avoid redundant network round-trip; use current conversation.You already applied conversations and selected one in a prior effect. Use the current selection instead of fetching again.
- const [conversation] = await editorEngine.chat.conversation.getConversations( - editorEngine.projectId, - ); - - if (!conversation) { - throw new Error('No conversation found'); - } - - await editorEngine.chat.conversation.selectConversation(conversation.id); + const current = editorEngine.chat.conversation.current; + if (!current) { + throw new Error('No conversation found'); + } + await editorEngine.chat.conversation.selectConversation(current.id);
94-101: Localize user-visible labels (“user image”).Hardcoded display names can appear in the UI. Use next-intl (similar to other components) or pass labels down from UI.
120-137: Localize toast messages.Replace hardcoded ‘Failed to complete/resume create request’ strings with translated messages.
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (3)
154-158: Localize ASK placeholder.Replace hardcoded placeholder with a translation key.
- if (chatMode === ChatType.ASK) { - return 'Ask a question about your project...'; - } + if (chatMode === ChatType.ASK) { + return t(transKeys.editor.panels.edit.tabs.chat.input.placeholderAsk); + }
412-418: Localize the Stop tooltip.Avoid hardcoded 'Stop response'.
- <TooltipContent>{'Stop response'}</TooltipContent> + <TooltipContent>{t(transKeys.editor.panels.edit.tabs.chat.input.stopResponse)}</TooltipContent>
146-150: Localize toast messages.User-visible toasts should use next-intl.
Examples to update:
- “Failed to send message. Please try again.”
- “Screenshot added to chat”
- “Failed to capture screenshot. Error: …”
Also applies to: 266-269
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (9)
112-113: Fix analytics: log the actualchatTypeused for the edit.- posthog.capture('user_edit_message', { type: ChatType.EDIT }); + posthog.capture('user_edit_message', { type: chatType });
114-114: Remove debug log.- console.log('[RERENDER] editMessage');
129-138: Avoid in‑place mutation ofmessage; create an updated copy for predictability.- message.metadata = { - ...message.metadata, - context: updatedContext, - conversationId, - createdAt: message.metadata?.createdAt ?? new Date(), - checkpoints: message.metadata?.checkpoints ?? [], - }; - message.parts = [{ type: 'text', text: newContent }]; - - setMessages(jsonClone([...updatedMessages, message])); + const updatedMessage: ChatMessage = { + ...message, + metadata: { + ...message.metadata, + context: updatedContext, + conversationId, + createdAt: message.metadata?.createdAt ?? new Date(), + checkpoints: message.metadata?.checkpoints ?? [], + }, + parts: [{ type: 'text', text: newContent }], + }; + + setMessages(jsonClone([...updatedMessages, updatedMessage]));
140-145: Pass refreshed context on edit regenerate to keep server in sync.void regenerate({ body: { chatType, conversationId, + context: updatedContext, }, });
62-65: Avoid shadowingfinishReasonname for clarity.- onFinish: ({ message }) => { - const finishReason = message.metadata?.finishReason; - setFinishReason(finishReason ?? null); - }, + onFinish: ({ message }) => { + const reason = message.metadata?.finishReason; + setFinishReason(reason ?? null); + },
176-221: GuardapplyCommitand cleanup against unhandled promise rejections.- const applyCommit = async () => { - const lastUserMessage = messagesRef.current.findLast((m) => m.role === 'user'); + const applyCommit = async () => { + try { + const lastUserMessage = messagesRef.current.findLast((m) => m.role === 'user'); - if (!lastUserMessage) { - return; - } + if (!lastUserMessage) return; - const content = lastUserMessage.parts + const content = lastUserMessage.parts .map((p) => { if (p.type === 'text') { return p.text; } return ''; }) .join(''); - if (!content) { - return; - } + if (!content) return; - const { commit } = await editorEngine.versions.createCommit(content, false); - if (!commit) { - throw new Error('Failed to create commit'); - } + const { commit } = await editorEngine.versions.createCommit(content, false); + if (!commit) throw new Error('Failed to create commit'); - const messageWithCommit = attachCommitToUserMessage( - commit, - lastUserMessage, - conversationId, - ); - setMessages( - jsonClone( - messagesRef.current.map((m) => - m.id === lastUserMessage.id ? messageWithCommit : m, - ), - ), - ); + const messageWithCommit = attachCommitToUserMessage( + commit, + lastUserMessage, + conversationId, + ); + setMessages( + jsonClone( + messagesRef.current.map((m) => + m.id === lastUserMessage.id ? messageWithCommit : m, + ), + ), + ); + } catch (error) { + console.error('applyCommit failed:', error); + } }; @@ - void cleanupContext(); + void cleanupContext().catch(console.error); void fetchSuggestions(); void applyCommit();
99-107: TrimsendMessagedeps: remove unused/constant refs to reduce re‑creations.- [ - editorEngine.chat.context, - editorEngine.versions, - messagesRef, - setMessages, - regenerate, - conversationId, - posthog, - ], + [ + editorEngine.chat.context, + setMessages, + regenerate, + conversationId, + posthog, + ],
149-156: TrimeditMessagedeps: drop unusededitorEngine.versions.- [ - editorEngine.chat.context, - editorEngine.versions, - regenerate, - conversationId, - setMessages, - posthog, - ], + [ + editorEngine.chat.context, + regenerate, + conversationId, + setMessages, + posthog, + ],
120-121: Avoid hard‑coded user‑facing text; prefer i18n or typed error codes.Consider throwing a coded error (e.g., 'chat.error.messageNotFound') and translate at the UI with next-intl.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx(17 hunks)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx(1 hunks)apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx(3 hunks)apps/web/client/src/components/store/editor/chat/context.ts(7 hunks)apps/web/client/src/components/store/editor/chat/conversation.ts(4 hunks)packages/ai/src/prompt/ask.ts(1 hunks)packages/ai/src/tools/tools/branch.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use the any type unless necessary
Files:
packages/ai/src/tools/tools/branch.tspackages/ai/src/prompt/ask.tsapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/components/store/editor/chat/conversation.tsapps/web/client/src/components/store/editor/chat/context.ts
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using the any type unless absolutely necessary
Files:
packages/ai/src/tools/tools/branch.tspackages/ai/src/prompt/ask.tsapps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/components/store/editor/chat/conversation.tsapps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env insteadAvoid hardcoded user-facing text; use next-intl messages/hooks
Files:
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
apps/web/client/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks insteadUse path aliases @/* and ~/* for imports mapping to src/*
Files:
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/components/store/editor/chat/conversation.tsapps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Files:
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
apps/web/client/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Files:
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsxapps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
🧠 Learnings (9)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/**/*.tsx : observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.{ts,tsx} : Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client provider(s) behind a client boundary (ensure this provider file is a client component)
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/utils/supabase/client/index.ts : Use the browser Supabase client only in client components
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client/provider behind a single client boundary in src/trpc/react.tsx
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Do not use process.env in client code; import env from @/env instead
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
🧬 Code graph analysis (5)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (6)
packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/components/tools/tools.ts (1)
handleToolCall(186-218)apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (3)
getUserChatMessageFromString(18-35)prepareMessagesForSuggestions(6-16)attachCommitToUserMessage(38-68)apps/web/client/src/trpc/react.tsx (1)
api(23-23)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (6)
packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)packages/models/src/chat/suggestion.ts (1)
ChatSuggestion(9-12)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
SendMessage(18-18)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)apps/web/client/src/components/store/editor/chat/index.ts (1)
sendMessage(43-49)packages/models/src/chat/message/context.ts (1)
ImageMessageContext(38-41)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
applyConversations(20-28)packages/models/src/chat/message/context.ts (2)
MessageContext(53-59)ImageMessageContext(38-41)apps/web/client/src/components/store/editor/chat/context.ts (2)
context(31-33)context(35-37)
apps/web/client/src/components/store/editor/chat/conversation.ts (2)
packages/models/src/chat/conversation/index.ts (1)
ChatConversation(3-12)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (2)
MessageContext(53-59)ErrorMessageContext(43-46)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)
🔇 Additional comments (2)
packages/ai/src/tools/tools/branch.ts (1)
4-8: Enforce UUID + brand BranchId — update tests/mocks
- Evidence: DB and server already treat branch IDs as UUIDs (packages/db/src/schema/canvas/frame.ts — branchId: uuid(...); apps/web/client/src/server/api/routers/project/branch.ts validates with z.string().uuid(); new branches created with uuidv4()).
- Current: BRANCH_ID_SCHEMA accepts any non-empty string (packages/ai/src/tools/tools/branch.ts).
- Impact: Switching to z.string().uuid().brand<'BranchId'>() will break many tests/mocks that use non-UUID values (examples: packages/ai/test/tools/read-unit.test.ts, apps/web/client/test/tools/read.test.ts, apps/web/client/test/branch/manager.test.ts).
- Recommendation: Change BRANCH_ID_SCHEMA to require and brand UUIDs (z.string().uuid().brand<'BranchId'>()) and update tests/mocks to use valid UUIDs or add a test helper to generate/allow test UUIDs. If the system uses CUID2 or a custom format instead, use .cuid2() or a precise .regex(...) and keep the brand.
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
176-181: Confirm Array.prototype.findLast support or add a polyfill/fallbackAutomated checks show no browserslist config or polyfill dependency; ensure your target browsers support Array.prototype.findLast or replace it.
Location: apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (around lines 176–181).
- Options: add/adjust browserslist so build targets include browsers that support findLast; or add a polyfill (e.g., array.prototype.findlast or core-js); or replace with a safe fallback (reverse loop or messages.slice().reverse().find(...)).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (1)
81-87: Fix contentKey: include streamedMessage.id, not the last non-streaming id.Prevents stale keys during streaming updates.
- <ChatMessageList - contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`} - > + <ChatMessageList + contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${streamedMessage?.id ?? ''}` : ''}`} + >
🧹 Nitpick comments (8)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)
76-81: Don’t depend on a “private” underscore field; expose readiness via ChatManager.Replace
editorEngine.chat._sendMessageActionwith a public getter to avoid leaking internals.Apply in this file:
- if (creationRequest && processedRequestIdRef.current !== creationRequest.id && isProjectReady && editorEngine.chat._sendMessageAction) { + if (creationRequest && processedRequestIdRef.current !== creationRequest.id && isProjectReady && editorEngine.chat.hasSendAction) {And add this to ChatManager (see inline comment there):
+ get hasSendAction(): boolean { + return this._sendMessageAction !== null; + }
88-101: Context displayName hardcoded (“user image”).If this label is ever surfaced in UI, route it through next‑intl or a constants map.
107-116: Avoid redundant conversation fetch.You already applied/selected a conversation earlier; prefer
editorEngine.chat.conversation.currentand start a new one if missing, instead of re-querying.- const [conversation] = await editorEngine.chat.conversation.getConversations( - editorEngine.projectId, - ); - if (!conversation) { - throw new Error('No conversation found'); - } - await editorEngine.chat.conversation.selectConversation(conversation.id); + const current = editorEngine.chat.conversation.current; + if (!current) { + await editorEngine.chat.conversation.startNewConversation(); + }apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2)
23-26: Ensure this component is under a client boundary.This file uses React hooks and MobX observer. If not always imported from a client component, add
"use client"here.+ 'use client'; import type { EditMessage } from '@/app/project/[id]/_hooks/use-chat';
49-55: Remove duplicate keys.You set keys on child nodes and on the wrapper; keep the key only on the top-level list item to avoid noise.
- messageNode = <AssistantMessage key={message.id} message={message} />; + messageNode = <AssistantMessage message={message} />; @@ - <UserMessage - key={message.id} - onEditMessage={onEditMessage} - message={message} - /> + <UserMessage onEditMessage={onEditMessage} message={message} />Also applies to: 63-64
apps/web/client/src/components/store/editor/chat/context.ts (2)
103-108: Drop unnecessary async on getImageContext.It’s synchronous; removing async avoids needless microtasks.
- private async getImageContext(): Promise<ImageMessageContext[]> { + private getImageContext(): ImageMessageContext[] { const imageContext = this.context.filter( (context) => context.type === MessageContextType.IMAGE, ); return imageContext; }
197-207: Hardcoded user-facing strings in store layer.‘Project’, ‘Errors - …’, and ‘Unknown Branch’ are UI text. Move labeling to the UI (with next‑intl) and keep store data semantic, or centralize labels in a translatable constants module.
Also applies to: 219-227
apps/web/client/src/components/store/editor/chat/index.ts (1)
13-16: Encapsulate send action; avoid external access to underscored field.Make
_sendMessageActionprivate and expose ahasSendActiongetter used by callers (see use-start-project comment).- _sendMessageAction: SendMessage | null = null; + private _sendMessageAction: SendMessage | null = null; @@ setChatActions(sendMessage: SendMessage) { this._sendMessageAction = sendMessage; } + + get hasSendAction(): boolean { + return this._sendMessageAction !== null; + }Also applies to: 39-41
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx(2 hunks)apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx(2 hunks)apps/web/client/src/components/store/editor/chat/context.ts(6 hunks)apps/web/client/src/components/store/editor/chat/index.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env insteadAvoid hardcoded user-facing text; use next-intl messages/hooks
Files:
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks insteadUse path aliases @/* and ~/* for imports mapping to src/*
Files:
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/components/store/editor/chat/index.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain
apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'
Files:
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Do not use the any type unless necessary
Files:
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/components/store/editor/chat/index.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs
Files:
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
{apps,packages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using the any type unless absolutely necessary
Files:
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsxapps/web/client/src/components/store/editor/chat/index.tsapps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsxapps/web/client/src/components/store/editor/chat/context.ts
🧠 Learnings (1)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead
Applied to files:
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
🧬 Code graph analysis (4)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
applyConversations(20-28)packages/models/src/chat/message/context.ts (2)
MessageContext(53-59)ImageMessageContext(38-41)apps/web/client/src/components/store/editor/chat/context.ts (2)
context(31-33)context(35-37)
apps/web/client/src/components/store/editor/chat/index.ts (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
ConversationManager(11-156)apps/web/client/src/components/store/editor/chat/context.ts (1)
ChatContext(16-320)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
SendMessage(18-18)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (7)
packages/models/src/chat/message/message.ts (1)
ChatMessage(18-18)apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
EditMessage(19-23)apps/web/client/src/components/store/editor/index.tsx (1)
useEditorEngine(10-14)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
UserMessage(29-264)packages/ui/src/components/chat/chat-message-list.tsx (1)
ChatMessageList(53-53)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
StreamMessage(6-27)apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
ErrorMessage(11-63)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (2)
MessageContext(53-59)ErrorMessageContext(43-46)apps/web/client/src/components/store/editor/engine.ts (1)
EditorEngine(33-138)
🔇 Additional comments (1)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (1)
102-106: Empty CREATE prompt — confirm server or add a guard.creationData.context can produce an empty
prompt(e.g. only image contexts). Confirm the server accepts emptycontentforChatType.CREATE; otherwise prevent sending an empty string (early return) or supply a fallback/default prompt before callingeditorEngine.chat.sendMessage(prompt, ChatType.CREATE). Location: apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (sendMessage at ~line 116).
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Important
Improves SSE performance by optimizing chat message handling, updating models, and enhancing error management across multiple components and hooks.
getModelFromTypeinstream.tsto useOPEN_AI_GPT_5model.onFinishcallback instreamResponseinroute.tsto store final messages in the database.useChatContextwithuseChathook for better chat management.OverlayChatInputandChatInputcomponents to use new chat handling logic.ChatMessagesandStreamMessagecomponents to handle streaming messages more efficiently.ChatTabContentto manage chat tab state and interactions.ErrorMessagecomponent to handle usage limits and other errors.errorHandlerfunction instream.tsfor consistent error logging.replaceConversationMessagesinmessage.tsto replace messages in a transaction.useChatContextand related logic, consolidating chat handling inuseChathook.use-auto-scroll.tsxfor better user experience.This description was created by
for dd0f12d. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Improvements
Documentation