[Playground] ai chat demo#7833
Conversation
Co-authored-by: joaquim.verges <joaquim.verges@gmail.com>
|
Cursor Agent can help with this pull request. Just |
|
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
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. WalkthroughAdds an AI chat playground to apps/playground-web: Nebula streaming chat client and types, a Next.js chat page with streaming UI and wallet actions, Markdown/dialog UI components, example prompts, toast support, and nav/sidebar links. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ChatUI as ChatPageContent
participant API as /ai/chat (server)
participant Stream as SSE
participant Wallet as Wallet/Thirdweb
User->>ChatUI: Send message
ChatUI->>API: POST /ai/chat (stream: true, context)
API-->>Stream: SSE events (init/presence/delta/image/action/context/error)
loop Streamed events
Stream-->>ChatUI: event:data
alt delta/presence/image/context
ChatUI->>ChatUI: Update UI state
else action (sign_transaction/sign_swap)
ChatUI->>Wallet: Request sign/execute
Wallet-->>ChatUI: Result or rejection
ChatUI->>ChatUI: Append action outcome
else error
ChatUI->>ChatUI: Show error
end
end
sequenceDiagram
participant ChatUI
participant Resolver as resolveSchemeWithErrorHandler
participant Storage as thirdweb/storage
ChatUI->>Resolver: resolve URI (uri)
alt uri undefined
Resolver-->>ChatUI: undefined
else uri provided
Resolver->>Storage: resolveScheme({uri, client})
Storage-->>Resolver: resolved URL or error
alt success
Resolver-->>ChatUI: resolved URL
else error
Resolver-->>ChatUI: undefined (logged)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
📜 Recent review detailsConfiguration used: CodeRabbit UI ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
✨ Finishing Touches🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
…-content-to-playground-37db
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7833 +/- ##
==========================================
- Coverage 56.34% 56.33% -0.01%
==========================================
Files 905 905
Lines 58830 58834 +4
Branches 4149 4158 +9
==========================================
Hits 33147 33147
- Misses 25577 25582 +5
+ Partials 106 105 -1
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 21
🧹 Nitpick comments (11)
apps/playground-web/src/app/ai/data/examplePrompts.ts (1)
1-5: Export the ExamplePrompt type for reuse across the AI chat codeTo align with “Re-use shared types from @/types or local types.ts barrels” and avoid future duplication, export the local type so other modules (e.g., Chat UI) can import it from one place.
-type ExamplePrompt = { +export type ExamplePrompt = { title: string; message: string; interceptedReply?: string; };apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.ts (2)
12-13: Avoid blanket eslint disable; document rationale or adjust linting targetA no-restricted-syntax override without context can hide future issues. If this rule intentionally forbids direct usage of resolveScheme, consider:
- Adding a brief comment on why the override is necessary here, or
- Adjusting the rule to allow this specific API in this file/package.
18-19: Make the error log message scheme-agnosticThe function resolves arbitrary schemes, not only IPFS. Update the message to reflect that.
Apply this diff:
- console.error("error resolving ipfs url", options.uri, err); + console.error("error resolving URI scheme", options.uri, err);apps/playground-web/src/app/ai/api/fetchWithAuthToken.ts (1)
15-50: Provide a typed JSON helper to align with “return typed results” guidelineCurrent API returns a raw Response, and downstream callers parse JSON. Consider adding a generic helper that returns typed JSON to promote stronger typing and reduce duplication, while keeping the existing function for streaming or non-JSON flows.
For example (add alongside the existing export):
export async function fetchJsonWithAuthToken<T>(options: FetchWithKeyOptions): Promise<T> { const res = await fetchWithAuthToken(options); const contentType = res.headers.get("content-type"); if (res.status === 204 || !contentType || !contentType.includes("application/json")) { // If you expect empty responses, you can adjust the return type to T | null instead. return null as unknown as T; } return (await res.json()) as T; }If helpful, I can update session.ts to use this helper and add minimal types for the expected payloads.
apps/playground-web/src/components/ui/inline-code.tsx (1)
11-14: Optional: prevent awkward wrapping for inline codeConsider adding whitespace-nowrap to avoid breaking inline code mid-token.
Apply this diff:
- "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold", + "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold whitespace-nowrap",apps/playground-web/src/app/ai/components/ChatPageContent.tsx (1)
869-882: Avoid direct localStorage access without proper error boundariesWhile the code has try-catch blocks, localStorage operations should be abstracted into a utility function for better maintainability and testability.
Create a utility module for localStorage operations:
// utils/storage.ts const STORAGE_KEYS = { NEBULA_LAST_USED_CHAIN_IDS: "nebula-last-used-chain-ids", } as const; export function saveToLocalStorage<T>(key: string, value: T): boolean { try { if (typeof window === "undefined") return false; localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } export function getFromLocalStorage<T>(key: string): T | null { try { if (typeof window === "undefined") return null; const item = localStorage.getItem(key); return item ? JSON.parse(item) : null; } catch { return null; } } export function removeFromLocalStorage(key: string): boolean { try { if (typeof window === "undefined") return false; localStorage.removeItem(key); return true; } catch { return false; } }Then use it:
-function saveLastUsedChainIds(chainIds: string[] | undefined) { - try { - if (chainIds && chainIds.length > 0) { - localStorage.setItem( - NEBULA_LAST_USED_CHAIN_IDS_KEY, - JSON.stringify(chainIds), - ); - } else { - localStorage.removeItem(NEBULA_LAST_USED_CHAIN_IDS_KEY); - } - } catch { - // ignore local storage errors - } -} +function saveLastUsedChainIds(chainIds: string[] | undefined) { + if (chainIds && chainIds.length > 0) { + saveToLocalStorage(STORAGE_KEYS.NEBULA_LAST_USED_CHAIN_IDS, chainIds); + } else { + removeFromLocalStorage(STORAGE_KEYS.NEBULA_LAST_USED_CHAIN_IDS); + } +}apps/playground-web/src/components/ui/dialog.tsx (1)
47-50: Accessibility: Consider making the close button label configurableThe close button has a hardcoded "Close" label. For better internationalization support, consider making this configurable.
-const DialogContent = React.forwardRef< - React.ElementRef<typeof DialogPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> ->(({ className, children, ...props }, ref) => ( +const DialogContent = React.forwardRef< + React.ElementRef<typeof DialogPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { + closeLabel?: string; + } +>(({ className, children, closeLabel = "Close", ...props }, ref) => ( <DialogPortal> <DialogOverlay /> <DialogPrimitive.Content ref={ref} className={cn( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className, )} {...props} > {children} <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <XIcon className="h-4 w-4" /> - <span className="sr-only">Close</span> + <span className="sr-only">{closeLabel}</span> </DialogPrimitive.Close> </DialogPrimitive.Content> </DialogPortal> ));apps/playground-web/src/app/ai/api/chat.ts (4)
216-274: ExportChatStreamedResponsetype for reusabilityThe
ChatStreamedResponsetype is only used internally but could be useful for consumers of thepromptNebulafunction to properly type their stream handlers.-type ChatStreamedResponse = +export type ChatStreamedResponse =
134-135: Improve error handling with more descriptive loggingThe error messages for parsing failures could be more informative by including the action type and raw data.
} catch (e) { - console.error("failed to parse action data", e, { event }); + console.error(`Failed to parse sign_transaction action data`, e, { + event, + rawData: data.data + }); }} catch (e) { - console.error("failed to parse action data", e, { event }); + console.error(`Failed to parse sign_swap action data`, e, { + event, + rawData: data.data + }); }Also applies to: 148-149
209-211: Consider more descriptive handling for unhandled eventsThe warning for unhandled events could include the event type for better debugging.
default: { - console.warn("unhandled event", event); + console.warn(`Unhandled event type: ${(event as any).event}`, event); }
4-4: Consider using environment variable validationThe API URL construction could benefit from validation to ensure the environment variable is properly set.
-const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`; +const API_HOST = process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"; +if (!API_HOST) { + console.warn("NEXT_PUBLIC_API_URL not set, using default api.thirdweb.com"); +} +const API_URL = `https://${API_HOST}`;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
apps/playground-web/package.json(2 hunks)apps/playground-web/src/app/ai/api/chat.ts(1 hunks)apps/playground-web/src/app/ai/api/fetchWithAuthToken.ts(1 hunks)apps/playground-web/src/app/ai/api/session.ts(1 hunks)apps/playground-web/src/app/ai/api/types.ts(1 hunks)apps/playground-web/src/app/ai/chat/page.tsx(1 hunks)apps/playground-web/src/app/ai/components/ChatPageContent.tsx(1 hunks)apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.ts(1 hunks)apps/playground-web/src/app/ai/data/examplePrompts.ts(1 hunks)apps/playground-web/src/app/ai/stores.ts(1 hunks)apps/playground-web/src/app/layout.tsx(2 hunks)apps/playground-web/src/app/navLinks.ts(3 hunks)apps/playground-web/src/components/ui/dialog.tsx(1 hunks)apps/playground-web/src/components/ui/inline-code.tsx(1 hunks)apps/playground-web/src/components/ui/markdown-renderer.tsx(1 hunks)apps/portal/src/app/ai/sidebar.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Files:
apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.tsapps/playground-web/src/components/ui/inline-code.tsxapps/portal/src/app/ai/sidebar.tsxapps/playground-web/src/app/ai/stores.tsapps/playground-web/src/app/layout.tsxapps/playground-web/src/app/ai/data/examplePrompts.tsapps/playground-web/src/app/ai/api/types.tsapps/playground-web/src/app/ai/api/session.tsapps/playground-web/src/app/ai/chat/page.tsxapps/playground-web/src/components/ui/markdown-renderer.tsxapps/playground-web/src/app/ai/api/fetchWithAuthToken.tsapps/playground-web/src/app/ai/components/ChatPageContent.tsxapps/playground-web/src/app/navLinks.tsapps/playground-web/src/app/ai/api/chat.tsapps/playground-web/src/components/ui/dialog.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit Inference Engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.tsapps/playground-web/src/components/ui/inline-code.tsxapps/portal/src/app/ai/sidebar.tsxapps/playground-web/src/app/ai/stores.tsapps/playground-web/src/app/layout.tsxapps/playground-web/src/app/ai/data/examplePrompts.tsapps/playground-web/src/app/ai/api/types.tsapps/playground-web/src/app/ai/api/session.tsapps/playground-web/src/app/ai/chat/page.tsxapps/playground-web/src/components/ui/markdown-renderer.tsxapps/playground-web/src/app/ai/api/fetchWithAuthToken.tsapps/playground-web/src/app/ai/components/ChatPageContent.tsxapps/playground-web/src/app/navLinks.tsapps/playground-web/src/app/ai/api/chat.tsapps/playground-web/src/components/ui/dialog.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.tsapps/playground-web/src/components/ui/inline-code.tsxapps/playground-web/src/app/ai/stores.tsapps/playground-web/src/app/layout.tsxapps/playground-web/src/app/ai/data/examplePrompts.tsapps/playground-web/src/app/ai/api/types.tsapps/playground-web/src/app/ai/api/session.tsapps/playground-web/src/app/ai/chat/page.tsxapps/playground-web/src/components/ui/markdown-renderer.tsxapps/playground-web/src/app/ai/api/fetchWithAuthToken.tsapps/playground-web/src/app/ai/components/ChatPageContent.tsxapps/playground-web/src/app/navLinks.tsapps/playground-web/src/app/ai/api/chat.tsapps/playground-web/src/components/ui/dialog.tsx
🧠 Learnings (10)
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{tsx} : Expose `className` prop on root element of components for overrides
Applied to files:
apps/playground-web/src/components/ui/inline-code.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use `cn()` from `@/lib/utils` for conditional class logic
Applied to files:
apps/playground-web/src/components/ui/inline-code.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Import UI primitives from `@/components/ui/*` (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
Applied to files:
apps/portal/src/app/ai/sidebar.tsxapps/playground-web/src/components/ui/dialog.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Icons come from `lucide-react` or the project-specific `…/icons` exports – never embed raw SVG.
Applied to files:
apps/portal/src/app/ai/sidebar.tsxapps/playground-web/src/app/navLinks.ts
📚 Learning: 2025-07-07T21:21:47.488Z
Learnt from: saminacodes
PR: thirdweb-dev/js#7543
File: apps/portal/src/app/pay/page.mdx:4-4
Timestamp: 2025-07-07T21:21:47.488Z
Learning: In the thirdweb-dev/js repository, lucide-react icons must be imported with the "Icon" suffix (e.g., ExternalLinkIcon, RocketIcon) as required by the new linting rule, contrary to the typical lucide-react convention of importing without the suffix.
Applied to files:
apps/portal/src/app/ai/sidebar.tsxapps/playground-web/src/app/navLinks.ts
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Layouts should reuse `SidebarLayout` / `FullWidthSidebarLayout` (`@/components/blocks/SidebarLayout`).
Applied to files:
apps/portal/src/app/ai/sidebar.tsx
📚 Learning: 2025-08-07T17:24:31.965Z
Learnt from: MananTank
PR: thirdweb-dev/js#7812
File: apps/dashboard/src/app/(app)/team/~/~project/[[...paths]]/page.tsx:1-11
Timestamp: 2025-08-07T17:24:31.965Z
Learning: In Next.js App Router, page components (page.tsx files) are server components by default and do not require the "server-only" import directive. The "server-only" directive is primarily used for utility functions, API helpers, and data access modules that should never be included in the client bundle.
Applied to files:
apps/playground-web/src/app/ai/chat/page.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use `NavLink` for internal navigation with automatic active states in dashboard and playground apps
Applied to files:
apps/playground-web/src/app/navLinks.ts
📚 Learning: 2025-07-31T16:17:42.753Z
Learnt from: MananTank
PR: thirdweb-dev/js#7768
File: apps/playground-web/src/app/navLinks.ts:1-1
Timestamp: 2025-07-31T16:17:42.753Z
Learning: Configuration files that import and reference React components (like icon components from lucide-react) need the "use client" directive, even if they primarily export static data, because the referenced components need to be executed in a client context when used by other client components.
Applied to files:
apps/playground-web/src/app/navLinks.tsapps/playground-web/src/components/ui/dialog.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Reuse core UI primitives; avoid re-implementing buttons, cards, modals.
Applied to files:
apps/playground-web/src/components/ui/dialog.tsx
🧬 Code Graph Analysis (13)
apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.ts (2)
packages/thirdweb/src/exports/thirdweb.ts (1)
ThirdwebClient(25-25)packages/thirdweb/src/exports/storage.ts (1)
resolveScheme(12-12)
apps/playground-web/src/components/ui/inline-code.tsx (1)
apps/playground-web/src/lib/utils.ts (1)
cn(5-7)
apps/portal/src/app/ai/sidebar.tsx (1)
apps/portal/src/icons/index.ts (1)
ExternalLinkIcon(4-4)
apps/playground-web/src/app/ai/stores.ts (1)
apps/playground-web/src/app/ai/api/types.ts (1)
TruncatedSessionInfo(68-73)
apps/playground-web/src/app/layout.tsx (1)
apps/portal/src/components/ui/sonner.tsx (1)
Toaster(32-32)
apps/playground-web/src/app/ai/data/examplePrompts.ts (1)
apps/dashboard/src/@/components/chat/types.ts (1)
ExamplePrompt(1-5)
apps/playground-web/src/app/ai/api/session.ts (3)
apps/playground-web/src/app/ai/api/chat.ts (1)
NebulaContext(6-10)apps/playground-web/src/app/ai/api/fetchWithAuthToken.ts (1)
fetchWithAuthToken(15-50)apps/playground-web/src/app/ai/api/types.ts (4)
SessionInfo(40-54)UpdatedSessionInfo(56-61)DeletedSessionInfo(63-66)TruncatedSessionInfo(68-73)
apps/playground-web/src/app/ai/chat/page.tsx (3)
apps/playground-web/src/app/ai/components/ChatPageContent.tsx (1)
ChatPageContent(148-471)apps/playground-web/src/lib/client.ts (1)
THIRDWEB_CLIENT(20-43)apps/playground-web/src/app/layout.tsx (1)
metadata(24-28)
apps/playground-web/src/components/ui/markdown-renderer.tsx (3)
apps/playground-web/src/components/ui/inline-code.tsx (1)
InlineCode(8-19)apps/portal/src/components/code/code.client.tsx (1)
CodeClient(27-54)apps/playground-web/src/lib/utils.ts (1)
cn(5-7)
apps/playground-web/src/app/ai/components/ChatPageContent.tsx (9)
apps/playground-web/src/app/ai/api/types.ts (4)
NebulaUserMessageContent(22-22)NebulaTxData(75-80)NebulaUserMessage(24-27)NebulaSessionHistoryMessage(29-38)apps/playground-web/src/app/ai/api/chat.ts (2)
NebulaSwapData(12-42)NebulaContext(6-10)apps/playground-web/src/app/ai/data/examplePrompts.ts (1)
examplePrompts(83-108)packages/thirdweb/src/react/web/ui/components/text.tsx (1)
Link(44-62)packages/thirdweb/src/react/web/ui/components/Spinner.tsx (1)
Spinner(11-34)apps/playground-web/src/components/ui/markdown-renderer.tsx (1)
MarkdownRenderer(18-214)apps/dashboard/src/@/components/blocks/Img.tsx (1)
Img(17-87)apps/playground-web/src/app/ai/components/resolveSchemeWithErrorHandler.ts (1)
resolveSchemeWithErrorHandler(4-21)apps/playground-web/src/lib/client.ts (1)
THIRDWEB_CLIENT(20-43)
apps/playground-web/src/app/navLinks.ts (1)
apps/playground-web/src/components/blocks/full-width-sidebar-layout.tsx (1)
ShadcnSidebarLink(43-59)
apps/playground-web/src/app/ai/api/chat.ts (2)
apps/playground-web/src/app/ai/components/ChatPageContent.tsx (2)
NebulaSwapData(115-146)NebulaTxData(108-113)apps/playground-web/src/app/ai/api/types.ts (2)
NebulaUserMessage(24-27)NebulaTxData(75-80)
apps/playground-web/src/components/ui/dialog.tsx (1)
apps/playground-web/src/lib/utils.ts (1)
cn(5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build Packages
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: Size
- GitHub Check: Analyze (javascript)
🔇 Additional comments (12)
apps/playground-web/package.json (1)
21-21: New deps look appropriate and are pinned — LGTMfetch-event-stream (for SSE) and sonner (for toasts) are reasonable choices and pinned to exact versions, consistent with the repo’s dependency style.
Also applies to: 35-35
apps/playground-web/src/app/navLinks.ts (2)
3-3: Correct icon import namingUsing BotIcon with the "Icon" suffix complies with the repo rule for lucide-react icon naming. Good.
13-24: AI submenu and route existence confirmedI verified that
apps/playground-web/src/app/ai/chat/page.tsxexists. The AI > Chat API link is valid and no further changes are required.apps/portal/src/app/ai/sidebar.tsx (2)
1-1: Icon import naming is correctExternalLinkIcon follows the enforced "Icon" suffix rule for lucide-react imports.
15-19: Playground external link is clear and well-placedAdding the external Playground link under Chat API improves discoverability. No issues.
apps/playground-web/src/app/ai/api/types.ts (1)
8-11: Clarify image content shape (mutually exclusive fields?)Are image_url and b64 mutually exclusive (exactly one present)? If yes, refine the union for better type safety. If both can be present or both null based on API behavior, ignore this.
Optionally refine as:
- | { - type: "image"; - image_url: string | null; - b64: string | null; - } + | ( + | { type: "image"; image_url: string; b64: null } + | { type: "image"; image_url: null; b64: string } + )Confirm the API contract and I can apply the precise typing.
apps/playground-web/src/app/ai/stores.ts (1)
26-29: LGTM on store usagesTyped stores for new/deleted sessions are simple and sufficient for the playground use case.
apps/playground-web/src/components/ui/inline-code.tsx (1)
3-6: Props shape and className override look goodExposes className on the root element and uses cn(), matching our learned preference for override-friendly components.
apps/playground-web/src/components/ui/markdown-renderer.tsx (1)
16-16: No changes needed for CodeClient import pathThe import
import CodeClient from "../code/code.client";correctly points toapps/playground-web/src/components/code/code.client.tsx, whereCodeClientis defined and exported. The suggestion to import from the portal app is not applicable.Likely an incorrect or invalid review comment.
apps/playground-web/src/app/ai/components/ChatPageContent.tsx (1)
716-716: Ignore incorrect Spinner import suggestion
TheSpinneris correctly imported from your local UI components at../../../components/ui/Spinner/Spinner. It accepts aclassNameprop (notsizeorcolor) and defaults to the styles defined in its CSS module when noclassNameis provided. No changes are needed to the import or usage.Likely an incorrect or invalid review comment.
apps/playground-web/src/components/ui/dialog.tsx (1)
1-122: LGTM! Well-structured dialog component following UI patternsThe dialog component properly wraps Radix UI primitives with consistent styling and follows the established UI component patterns. Good use of forwardRef, proper TypeScript typing, and preservation of display names.
apps/playground-web/src/app/ai/api/chat.ts (1)
58-60: Potential runtime error with nullish coalescingThe use of
??withundefinedon line 59 is redundant and could mask issues. IfsessionIdisnull, it will passnullto the API instead ofundefined.Simplify the logic and ensure consistent handling:
body.context = { chain_ids: params.context.chainIds?.map(Number) || [], - session_id: params.context.sessionId ?? undefined, + session_id: params.context.sessionId || undefined, wallet_address: params.context.walletAddress, };Likely an incorrect or invalid review comment.
| @@ -0,0 +1,19 @@ | |||
| import { cn } from "@/lib/utils"; | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Mark as a Client Component
Per our app guidelines for playground components, add 'use client' at the top.
Apply this diff:
+'use client';
import { cn } from "@/lib/utils";📝 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 { cn } from "@/lib/utils"; | |
| 'use client'; | |
| import { cn } from "@/lib/utils"; |
🤖 Prompt for AI Agents
In apps/playground-web/src/components/ui/inline-code.tsx around line 1, this
file needs to be marked as a React client component; add a single line
containing 'use client' (exactly with quotes) as the very first line of the
file, before any imports, so the directive applies correctly.
2971127 to
211009b
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (4)
apps/playground-web/src/app/ai/api/chat.ts (4)
12-42: Type inconsistency: Missingvaluefield in transaction object.The NebulaSwapData transaction object is missing the optional
valuefield that exists in NebulaTxData and is expected by consuming components.Add the missing
valuefield to maintain consistency with NebulaTxData:type NebulaSwapData = { action: string; transaction: { chainId: number; to: `0x${string}`; data: `0x${string}`; + value?: string; };
44-50: Unused authToken parameter - security concern.The authToken parameter is accepted but never used in the request headers, which appears to be a security oversight since authentication should be properly implemented.
Either implement the Authorization header or remove the unused parameter:
const events = await stream(`${API_URL}/ai/chat`, { body: JSON.stringify(body), headers: { + Authorization: `Bearer ${params.authToken}`, "x-client-id": process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID!,Or if authentication isn't needed yet:
export async function promptNebula(params: { message: NebulaUserMessage; - authToken: string; handleStream: (res: ChatStreamedResponse) => void; abortController: AbortController; context: undefined | NebulaContext; }) {
68-70: Critical: Remove hardcoded secret key immediately.A hardcoded secret key is exposed in the codebase, which is a critical security vulnerability that must be addressed before deployment.
Remove the hardcoded secret and use proper environment variables:
headers: { "x-client-id": process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID!, - // FIXME REMOVE - "x-secret-key": - "7NFrTzBN9Y2Eca6Rl60uxT3Dwew4D9YaYHjoD_3Y2GDvkaejLFiodFDcuzGJqu0mc8PVCAi9M4Y3j6Ql_ZVRyQ", + // Add proper authentication once the backend is ready "Content-Type": "application/json", },
294-297: Missingrequest_idfield in action event type.The ChatStreamedEvent type for "action" events is missing the
request_idfield that's being parsed and used in the handler (lines 130 and 144).Add the missing field to properly type the raw event:
| { event: "action"; type: "sign_transaction" | "sign_swap"; data: string; + request_id: string; }
🧹 Nitpick comments (1)
apps/playground-web/src/app/ai/api/chat.ts (1)
77-83: Improve type safety in event stream processing.The type assertion on line 82 bypasses TypeScript's type safety. Consider adding runtime validation.
Add runtime validation for the event structure:
for await (const _event of events) { if (!_event.data) { continue; } - const event = _event as ChatStreamedEvent; + const event = _event as ChatStreamedEvent; + + if (!event.event) { + console.warn("Invalid event structure:", event); + continue; + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/playground-web/src/app/ai/api/chat.ts(1 hunks)apps/playground-web/src/app/ai/api/types.ts(1 hunks)apps/playground-web/src/app/ai/components/ChatPageContent.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/playground-web/src/app/ai/api/types.ts
- apps/playground-web/src/app/ai/components/ChatPageContent.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Files:
apps/playground-web/src/app/ai/api/chat.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit Inference Engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/playground-web/src/app/ai/api/chat.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/playground-web/src/app/ai/api/chat.ts
🧠 Learnings (3)
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use `Authorization: Bearer` header – never embed tokens in URLs
Applied to files:
apps/playground-web/src/app/ai/api/chat.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Keep tokens secret via internal API routes or server actions
Applied to files:
apps/playground-web/src/app/ai/api/chat.ts
📚 Learning: 2025-05-27T19:56:16.920Z
Learnt from: MananTank
PR: thirdweb-dev/js#7177
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_hooks/useTokenTransfers.ts:41-44
Timestamp: 2025-05-27T19:56:16.920Z
Learning: When reviewing hooks that use environment variables like NEXT_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID for API calls, MananTank prefers not to add explicit validation checks for these variables, trusting they will be set in the deployment environment.
Applied to files:
apps/playground-web/src/app/ai/api/chat.ts
🧬 Code Graph Analysis (1)
apps/playground-web/src/app/ai/api/chat.ts (1)
apps/playground-web/src/app/ai/api/types.ts (2)
NebulaUserMessage(24-27)NebulaTxData(56-61)
🔇 Additional comments (8)
apps/playground-web/src/app/ai/api/chat.ts (8)
1-2: LGTM! Clean imports with proper typing.The imports are well-structured and follow TypeScript best practices.
4-4: Environment variable usage looks correct.The API_URL construction with fallback to a default value is appropriate for this configuration.
6-10: Well-defined context type structure.The NebulaContext type properly captures the required context fields with appropriate nullability.
56-62: LGTM! Context transformation logic is well-implemented.The context mapping from string chainIds to numbers and proper handling of optional fields is correct.
85-93: LGTM! Delta event handling is correct.The JSON parsing and data transformation for delta events is properly implemented.
121-153: Well-structured action event handling with proper error handling.The action event processing correctly handles both sign_transaction and sign_swap types with appropriate error handling and logging.
209-212: Good defensive programming with unhandled event logging.The default case properly logs unhandled events, which will help with debugging and future event type additions.
216-274: Well-defined response type union.The ChatStreamedResponse type union properly captures all possible response shapes with appropriate typing for each event type.
211009b to
070ad59
Compare
070ad59 to
81cce59
Compare
[Playground] Feature: Port Nebula AI Chat to Playground
Notes for the reviewer
ChatPageContentcomponent toapps/playground-web.dialog.tsxcomponent, as it was missing inapps/playground-web./ai/chatroute and adds a corresponding link to the sidebar navigation.NEXT_PUBLIC_THIRDWEB_CLIENT_IDandNEXT_PUBLIC_NEBULA_URLenvironment variables, which are currently placeholders. The authentication is also mocked.How to test
/ai/chat.Slack Thread
PR-Codex overview
This PR introduces new features and UI components for an AI chat application, enhancing user interaction with improved messaging capabilities, error handling, and integrating third-party libraries for better functionality.
Detailed summary
<Toaster />component for notifications inlayout.tsx.InlineCodecomponent ininline-code.tsx.sidebar.tsx.resolveSchemeWithErrorHandler.ts.ChatPagecomponent withThirdwebProviderinpage.tsx.types.ts.navLinks.ts.examplePrompts.tsfor AI interactions.dialog.tsxfor better user experience.markdown-renderer.tsx.chat.tsandChatPageContent.tsx.Summary by CodeRabbit
New Features
Chores