Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/components/Messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { FilePartRenderer } from "./FilePartRenderer";
import { MessageErrorState } from "./MessageErrorState";
import { MessageEditor } from "./MessageEditor";
import { FeedbackInput } from "./FeedbackInput";
import { ShimmerText } from "./ShimmerText";
import { Shimmer } from "@/components/ai-elements/shimmer";
import { AllFilesDialog } from "./AllFilesDialog";
import { BranchIndicator } from "./BranchIndicator";
import { FinishReasonNotice } from "./FinishReasonNotice";
Expand Down Expand Up @@ -523,9 +523,9 @@ export const Messages = ({
{/* Upload status - shown where assistant response will appear */}
{uploadStatus?.isUploading && (
<div className="flex flex-col items-start">
<ShimmerText className="text-sm">
{uploadStatus.message}...
</ShimmerText>
<Shimmer className="text-sm">
{`${uploadStatus.message}...`}
</Shimmer>
</div>
)}

Expand Down
39 changes: 0 additions & 39 deletions app/components/ShimmerText.tsx

This file was deleted.

10 changes: 5 additions & 5 deletions app/components/TerminalCodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { Terminal } from "lucide-react";
import { codeToHtml } from "shiki";
import { ShimmerText } from "./ShimmerText";
import { Shimmer } from "@/components/ai-elements/shimmer";

interface TerminalCodeBlockProps {
command: string;
Expand Down Expand Up @@ -171,9 +171,9 @@ const AnsiCodeBlock = ({
if (!htmlContent && (isRendering || code)) {
return (
<div className="px-4 py-4 text-muted-foreground">
<ShimmerText>
<Shimmer>
{isStreaming ? "Processing output..." : "Rendering output..."}
</ShimmerText>
</Shimmer>
</div>
);
}
Expand Down Expand Up @@ -241,7 +241,7 @@ export const TerminalCodeBlock = ({
<div className="flex-1 min-h-0 overflow-hidden">
{isExecuting && !output && status === "streaming" ? (
<div className="px-4 py-4 text-muted-foreground">
<ShimmerText>Executing command</ShimmerText>
<Shimmer>Executing command</Shimmer>
</div>
) : (
<AnsiCodeBlock
Expand All @@ -265,7 +265,7 @@ export const TerminalCodeBlock = ({
<div className="h-full w-full overflow-auto">
{isExecuting && !output && status === "streaming" ? (
<div className="px-4 py-4 text-muted-foreground h-full flex items-start">
<ShimmerText>Executing command</ShimmerText>
<Shimmer>Executing command</Shimmer>
</div>
) : (
<AnsiCodeBlock
Expand Down
17 changes: 0 additions & 17 deletions app/components/__tests__/ShimmerText.test.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions app/components/tools/SummarizationHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UIMessage } from "@ai-sdk/react";
import { WandSparkles } from "lucide-react";
import { ShimmerText } from "../ShimmerText";
import { Shimmer } from "@/components/ai-elements/shimmer";

interface SummarizationHandlerProps {
message: UIMessage;
Expand All @@ -20,7 +20,7 @@ export const SummarizationHandler = ({
>
<WandSparkles className="w-4 h-4 text-muted-foreground" />
{part.data.status === "started" ? (
<ShimmerText className="text-sm">{part.data.message}...</ShimmerText>
<Shimmer className="text-sm">{`${part.data.message}...`}</Shimmer>
) : (
<span className="text-sm text-muted-foreground">
{part.data.message}
Expand Down
64 changes: 64 additions & 0 deletions components/ai-elements/shimmer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import { cn } from "@/lib/utils";
import { motion } from "motion/react";
import {
type CSSProperties,
type ElementType,
type JSX,
memo,
useMemo,
} from "react";

export type TextShimmerProps = {
children: string;
as?: ElementType;
className?: string;
duration?: number;
spread?: number;
};

const ShimmerComponent = ({
children,
as: Component = "p",
className,
duration = 2,
spread = 2,
}: TextShimmerProps) => {
const MotionComponent = motion.create(
Component as keyof JSX.IntrinsicElements,
);

const dynamicSpread = useMemo(
() => (children?.length ?? 0) * spread,
[children, spread],
);

return (
<MotionComponent
animate={{ backgroundPosition: "0% center" }}
className={cn(
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
className,
)}
initial={{ backgroundPosition: "100% center" }}
style={
{
"--spread": `${dynamicSpread}px`,
backgroundImage:
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
} as CSSProperties
}
transition={{
repeat: Number.POSITIVE_INFINITY,
duration,
ease: "linear",
}}
Comment on lines +28 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid recreating the motion wrapper every render

ESLint (react-hooks/static-components) is erroring because motion.create runs inside the render path, so the motion component is rebuilt on each render and any local state would reset. Memoize the factory on the Component reference to keep lint happy and avoid unnecessary churn.

Apply this diff:

-  const MotionComponent = motion.create(
-    Component as keyof JSX.IntrinsicElements,
-  );
+  const MotionComponent = useMemo(
+    () => motion.create(Component as keyof JSX.IntrinsicElements),
+    [Component],
+  );
📝 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.

Suggested change
const MotionComponent = motion.create(
Component as keyof JSX.IntrinsicElements,
);
const dynamicSpread = useMemo(
() => (children?.length ?? 0) * spread,
[children, spread],
);
return (
<MotionComponent
animate={{ backgroundPosition: "0% center" }}
className={cn(
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
className,
)}
initial={{ backgroundPosition: "100% center" }}
style={
{
"--spread": `${dynamicSpread}px`,
backgroundImage:
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
} as CSSProperties
}
transition={{
repeat: Number.POSITIVE_INFINITY,
duration,
ease: "linear",
}}
const MotionComponent = useMemo(
() => motion.create(Component as keyof JSX.IntrinsicElements),
[Component],
);
const dynamicSpread = useMemo(
() => (children?.length ?? 0) * spread,
[children, spread],
);
return (
<MotionComponent
animate={{ backgroundPosition: "0% center" }}
className={cn(
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
className,
)}
initial={{ backgroundPosition: "100% center" }}
style={
{
"--spread": `${dynamicSpread}px`,
backgroundImage:
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
} as CSSProperties
}
transition={{
repeat: Number.POSITIVE_INFINITY,
duration,
ease: "linear",
}}
🧰 Tools
🪛 ESLint

[error] 38-38: Error: Cannot create components during render

Components created during render will reset their state each time they are created. Declare components outside of render.

/home/jailuser/git/components/ai-elements/shimmer.tsx:38:6
36 |
37 | return (

38 | <MotionComponent
| ^^^^^^^^^^^^^^^ This component is created during render
39 | animate={{ backgroundPosition: "0% center" }}
40 | className={cn(
41 | "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",

/home/jailuser/git/components/ai-elements/shimmer.tsx:28:27
26 | spread = 2,
27 | }: TextShimmerProps) => {

28 | const MotionComponent = motion.create(
| ^^^^^^^^^^^^^^
29 | Component as keyof JSX.IntrinsicElements,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 | );
| ^^^^ The component is created during render here
31 |
32 | const dynamicSpread = useMemo(
33 | () => (children?.length ?? 0) * spread,

(react-hooks/static-components)

🤖 Prompt for AI Agents
In components/ai-elements/shimmer.tsx around lines 28 to 57, motion.create is
being called directly during render which recreates the MotionComponent each
render; wrap the factory call in a memo (e.g. useMemo(() =>
motion.create(Component as keyof JSX.IntrinsicElements), [Component])) or useRef
initialized once to preserve the same MotionComponent between renders so the
motion wrapper is not rebuilt and the react-hooks/static-components ESLint error
is resolved.

>
{children}
</MotionComponent>
);
};

export const Shimmer = memo(ShimmerComponent);
4 changes: 2 additions & 2 deletions components/ui/tool-block.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { ShimmerText } from "@/app/components/ShimmerText";
import { Shimmer } from "@/components/ai-elements/shimmer";

interface ToolBlockProps {
icon: React.ReactNode;
Expand Down Expand Up @@ -43,7 +43,7 @@ const ToolBlock: React.FC<ToolBlockProps> = ({
</div>
<div className="max-w-[100%] truncate text-muted-foreground relative top-[-1px]">
<span className="text-[13px]">
{isShimmer ? <ShimmerText>{action}</ShimmerText> : action}
{isShimmer ? <Shimmer>{action}</Shimmer> : action}
</span>
{target && (
<span className="text-[12px] font-mono ml-[6px] text-muted-foreground/70">
Expand Down
30 changes: 19 additions & 11 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ import type {
FunctionReference,
} from "convex/server";

/**
* A utility for referencing Convex functions in your app's API.
*
* Usage:
* ```js
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
declare const fullApi: ApiFromModules<{
chats: typeof chats;
crons: typeof crons;
Expand All @@ -45,14 +37,30 @@ declare const fullApi: ApiFromModules<{
userCustomization: typeof userCustomization;
userDeletion: typeof userDeletion;
}>;
declare const fullApiWithMounts: typeof fullApi;

/**
* A utility for referencing Convex functions in your app's public API.
*
* Usage:
* ```js
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
export declare const api: FilterApi<
typeof fullApiWithMounts,
typeof fullApi,
FunctionReference<any, "public">
>;

/**
* A utility for referencing Convex functions in your app's internal API.
*
* Usage:
* ```js
* const myFunctionReference = internal.myModule.myFunction;
* ```
*/
export declare const internal: FilterApi<
typeof fullApiWithMounts,
typeof fullApi,
FunctionReference<any, "internal">
>;

Expand Down
16 changes: 5 additions & 11 deletions convex/_generated/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import {
ActionBuilder,
AnyComponents,
HttpActionBuilder,
MutationBuilder,
QueryBuilder,
Expand All @@ -19,15 +18,9 @@ import {
GenericQueryCtx,
GenericDatabaseReader,
GenericDatabaseWriter,
FunctionReference,
} from "convex/server";
import type { DataModel } from "./dataModel.js";

type GenericCtx =
| GenericActionCtx<DataModel>
| GenericMutationCtx<DataModel>
| GenericQueryCtx<DataModel>;

/**
* Define a query in this Convex app's public API.
*
Expand Down Expand Up @@ -92,11 +85,12 @@ export declare const internalAction: ActionBuilder<DataModel, "internal">;
/**
* Define an HTTP action.
*
* This function will be used to respond to HTTP requests received by a Convex
* deployment if the requests matches the path and method where this action
* is routed. Be sure to route your action in `convex/http.js`.
* The wrapped function will be used to respond to HTTP requests received
* by a Convex deployment if the requests matches the path and method where
* this action is routed. Be sure to route your httpAction in `convex/http.js`.
*
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
* @param func - The function. It receives an {@link ActionCtx} as its first argument
* and a Fetch API `Request` object as its second.
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
*/
export declare const httpAction: HttpActionBuilder;
Expand Down
13 changes: 8 additions & 5 deletions convex/_generated/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
internalActionGeneric,
internalMutationGeneric,
internalQueryGeneric,
componentsGeneric,
} from "convex/server";

/**
Expand Down Expand Up @@ -81,10 +80,14 @@ export const action = actionGeneric;
export const internalAction = internalActionGeneric;

/**
* Define a Convex HTTP action.
* Define an HTTP action.
*
* @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
* as its second.
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
* The wrapped function will be used to respond to HTTP requests received
* by a Convex deployment if the requests matches the path and method where
* this action is routed. Be sure to route your httpAction in `convex/http.js`.
*
* @param func - The function. It receives an {@link ActionCtx} as its first argument
* and a Fetch API `Request` object as its second.
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
*/
export const httpAction = httpActionGeneric;
9 changes: 5 additions & 4 deletions convex/fileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { v } from "convex/values";
import { countTokens } from "gpt-tokenizer";
import { getDocument } from "pdfjs-serverless";
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
import { JSONLoader } from "langchain/document_loaders/fs/json";
import mammoth from "mammoth";
import WordExtractor from "word-extractor";
import { isBinaryFile } from "isbinaryfile";
Expand Down Expand Up @@ -313,9 +312,11 @@ const processCsvFile = async (csv: Blob): Promise<FileItemChunk[]> => {
};

const processJsonFile = async (json: Blob): Promise<FileItemChunk[]> => {
const loader = new JSONLoader(json);
const docs = await loader.load();
const completeText = docs.map((doc) => doc.pageContent).join(" ");
const fileBuffer = Buffer.from(await json.arrayBuffer());
const textDecoder = new TextDecoder("utf-8");
const jsonText = textDecoder.decode(fileBuffer);
const parsedJson = JSON.parse(jsonText);
const completeText = JSON.stringify(parsedJson, null, 2);

return [
{
Expand Down
Loading