From cb90378cd49186f01b9472ee6a78dd3f76aa4529 Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Tue, 9 Sep 2025 06:23:54 +0000 Subject: [PATCH 1/7] chore: update configuration files and workflows - Refactored .gitignore to simplify environment file handling and added cspell cache. - Updated biome.jsonc schema reference and adjusted file inclusion/exclusion rules. - Added devcontainer.json for development environment setup. - Introduced GitHub action for setup to streamline Node and pnpm installation. - Modified CI workflow to include type checking and Biome linting. - Updated Playwright workflow to utilize the new setup action. --- .devcontainer/devcontainer.json | 54 +++++++++++++++++++++++++ .github/actions/setup/action.yml | 33 +++++++++++++++ .github/workflows/lint.yml | 45 ++++++++++++--------- .github/workflows/playwright.yml | 34 ++-------------- .gitignore | 13 +++--- biome.jsonc | 69 ++++++++++++++++++-------------- 6 files changed, 162 insertions(+), 86 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/actions/setup/action.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..16d07fa054 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,54 @@ +{ + "name": "ai-chatbot", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "forwardPorts": [ + 3000 + ], + "portsAttributes": { + "3000": { + "label": "Web", + "onAutoForward": "notify" + } + }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": true + }, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + "containerEnv": {}, + "remoteEnv": {}, + "customizations": { + "vscode": { + "extensions": [ + // TypeScript + "better-ts-errors.better-ts-errors", + // Other + "bradlc.vscode-tailwindcss", + "streetsidesoftware.code-spell-checker", + "biomejs.biome", + "aaron-bond.better-comments", + "formulahendry.auto-rename-tag" + ], + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "debug.internalConsoleOptions": "neverOpen", + "editor.formatOnPaste": true, + "editor.guides.bracketPairs": "active", + "scm.defaultViewMode": "tree", + "diffEditor.diffAlgorithm": "advanced", + "diffEditor.experimental.showMoves": true, + "diffEditor.renderSideBySide": false, + "files.watcherExclude": { + "**/node_modules/**": true + }, + // Prettifies the response with emojis and such. + "betterTypeScriptErrors.prettify": true + } + } + }, + "postCreateCommand": { + "web": "pnpm install" + } + } \ No newline at end of file diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000000..357c47b6d2 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,33 @@ +name: Check setup +description: Set up Node, and pnpm, prime caches, and install dependencies. + +runs: + using: composite + steps: + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Get pnpm store path + id: pnpm-cache + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Cache pnpm store + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1d57a670b7..4046af9860 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,25 +1,32 @@ -name: Lint +name: CI + on: push: jobs: - build: + types: + name: TypeScript runs-on: ubuntu-22.04 - strategy: - matrix: - node-version: [20] + steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: "pnpm" - - name: Install dependencies - run: pnpm install - - name: Run lint - run: pnpm lint + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run type check + run: pnpm typecheck + + biome: + name: Biome + runs-on: ubuntu-22.04 + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run Biome + run: pnpm check \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 6e18c3ec60..df6ee65783 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,36 +20,8 @@ jobs: with: fetch-depth: 1 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: latest - run_install: false - - - name: Get pnpm store directory - id: pnpm-cache - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v3 - with: - path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: "pnpm" - - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Setup + uses: ./.github/actions/setup - name: Cache Playwright browsers uses: actions/cache@v3 @@ -70,4 +42,4 @@ jobs: with: name: playwright-report path: playwright-report/ - retention-days: 7 + retention-days: 7 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 54b31f9fcf..0750257b79 100644 --- a/.gitignore +++ b/.gitignore @@ -24,20 +24,19 @@ yarn-error.log* .pnpm-debug.log* # local env files -.env.local -.env.development.local -.env.test.local -.env.production.local +.env* +!.env.example # turbo .turbo - -.env .vercel -.env*.local +.pnpm-store # Playwright /test-results/ /playwright-report/ /blob-report/ /playwright/* + +# cspell +.cspellcache \ No newline at end of file diff --git a/biome.jsonc b/biome.jsonc index d227936e56..399f3f39a1 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,15 +1,21 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "node_modules/@biomejs/biome/configuration_schema.json", "files": { "ignoreUnknown": false, - "ignore": [ - "**/pnpm-lock.yaml", - "lib/db/migrations", - "lib/editor/react-renderer.tsx", - "node_modules", - ".next", - "public", - ".vercel" + "includes": [ + "**", + "!server/db/migrations", + "!lib/editor/react-renderer.tsx", + "!node_modules", + "!**/.next", + "!**/dist", + "!**/public", + "!.github", + "!.vercel", + "!pnpm-lock.yaml", + "!bun.lock", + "!src/components/ui", + "!**/*.grit" ] }, "vcs": { @@ -38,16 +44,20 @@ "level": "warn", "options": { "ignoreNonDom": false, - "allowInvalidRoles": ["none", "text"] + "allowInvalidRoles": [ + "none", + "text" + ] } }, "useSemanticElements": "off", // Rule is buggy, revisit later "noSvgWithoutTitle": "off", // We do not intend to adhere to this rule "useMediaCaption": "off", // We would need a cultural change to turn this on "noAutofocus": "off", // We're highly intentional about when we use autofocus - "noBlankTarget": "off", // Covered by Conformance "useFocusableInteractive": "off", // Disable focusable interactive element requirement "useAriaPropsForRole": "off", // Disable required ARIA attributes check + "noStaticElementInteractions": "warn", + "useValidAutocomplete": "warn", "useKeyWithClickEvents": "off" // Disable keyboard event requirement with click events }, "complexity": { @@ -55,20 +65,19 @@ "noForEach": "off", // forEach is too familiar to ban "noUselessSwitchCase": "off", // Turned off due to developer preferences "noUselessThisAlias": "off", // Turned off due to developer preferences - "noBannedTypes": "off" + "noUselessContinue": "off" // Turned off due to developer preferences }, "correctness": { "noUnusedImports": "warn", // Not in recommended ruleset, turning on manually - "useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually - "noNewSymbol": "warn", // Not in recommended ruleset, turning on manually "useJsxKeyInIterable": "off", // Rule is buggy, revisit later - "useExhaustiveDependencies": "off", // Community feedback on this rule has been poor, we will continue with ESLint - "noUnnecessaryContinue": "off" // Turned off due to developer preferences + "useExhaustiveDependencies": "off" // Community feedback on this rule has been poor, we will continue with ESLint }, "security": { - "noDangerouslySetInnerHtml": "off" // Covered by Conformance + "noDangerouslySetInnerHtml": "off", // Covered by Conformance + "noBlankTarget": "off" // Covered by Conformance }, "style": { + "useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually "useFragmentSyntax": "warn", // Not in recommended ruleset, turning on manually "noYodaExpression": "warn", // Not in recommended ruleset, turning on manually "useDefaultParameterLast": "warn", // Not in recommended ruleset, turning on manually @@ -77,16 +86,13 @@ "noUselessElse": "off" // Stylistic opinion }, "suspicious": { - "noExplicitAny": "off" // We trust Vercelians to use any only when necessary - }, - "nursery": { - "noStaticElementInteractions": "warn", + "noExplicitAny": "off", // We trust Vercelians to use any only when necessary "noHeadImportInDocument": "warn", "noDocumentImportInPage": "warn", "noDuplicateElseIf": "warn", - "noIrregularWhitespace": "warn", - "useValidAutocomplete": "warn" - } + "noIrregularWhitespace": "warn" + }, + "nursery": {} } }, "javascript": { @@ -114,15 +120,20 @@ } }, "css": { - "formatter": { "enabled": false }, - "linter": { "enabled": false } + "formatter": { + "enabled": false + }, + "linter": { + "enabled": false + } }, - "organizeImports": { "enabled": false }, "overrides": [ // Playwright requires an object destructure, even if empty // https://github.com/microsoft/playwright/issues/30007 { - "include": ["playwright/**"], + "includes": [ + "playwright/**" + ], "linter": { "rules": { "correctness": { @@ -132,4 +143,4 @@ } } ] -} +} \ No newline at end of file From c31cd99c3e1a987c11f5a146e935bf857c2af8bb Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Wed, 10 Sep 2025 16:44:09 +0000 Subject: [PATCH 2/7] refactor: update configuration files and component styles - Updated biome.jsonc schema reference and adjusted file inclusion/exclusion rules. - Changed components.json style to "new-york" and added iconLibrary. - Added @ai-sdk/openai dependency in package.json and updated pnpm-lock.yaml. - Refactored message actions component to improve copy functionality and UI. - Enhanced multimodal input component with new action menus and improved attachment handling. - Updated various components for better styling and responsiveness, including context menus and suggested actions. - Cleaned up unused code and improved overall component structure. --- app/globals.css | 18 -- biome.jsonc | 53 ++--- components.json | 8 +- components/elements/actions.tsx | 2 +- components/elements/context.tsx | 2 +- components/elements/prompt-input.tsx | 62 ++++- components/message-actions.tsx | 51 ++++- components/message.tsx | 8 +- components/multimodal-input.tsx | 324 ++++++++++++++------------- components/preview-attachment.tsx | 33 +-- components/sidebar-history-item.tsx | 6 +- components/suggested-actions.tsx | 2 +- package.json | 1 + pnpm-lock.yaml | 28 +++ 14 files changed, 360 insertions(+), 238 deletions(-) diff --git a/app/globals.css b/app/globals.css index 4be4d707ec..dd8a957638 100644 --- a/app/globals.css +++ b/app/globals.css @@ -140,24 +140,6 @@ --color-sidebar-ring: var(--sidebar-ring); } -/* - The default border color has changed to `currentcolor` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. -*/ -@layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } -} - @utility text-balance { text-wrap: balance; } diff --git a/biome.jsonc b/biome.jsonc index d4e935e6fc..36665c0b3f 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,21 +1,15 @@ { - "$schema": "node_modules/@biomejs/biome/configuration_schema.json", + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "files": { "ignoreUnknown": false, - "includes": [ - "**", - "!server/db/migrations", - "!lib/editor/react-renderer.tsx", - "!node_modules", - "!**/.next", - "!**/dist", - "!**/public", - "!.github", - "!.vercel", - "!pnpm-lock.yaml", - "!bun.lock", - "!src/components/ui", - "!**/*.grit" + "ignore": [ + "**/pnpm-lock.yaml", + "lib/db/migrations", + "lib/editor/react-renderer.tsx", + "node_modules", + ".next", + "public", + ".vercel" ] }, "vcs": { @@ -44,20 +38,16 @@ "level": "warn", "options": { "ignoreNonDom": false, - "allowInvalidRoles": [ - "none", - "text" - ] + "allowInvalidRoles": ["none", "text"] } }, "useSemanticElements": "off", // Rule is buggy, revisit later "noSvgWithoutTitle": "off", // We do not intend to adhere to this rule "useMediaCaption": "off", // We would need a cultural change to turn this on "noAutofocus": "off", // We're highly intentional about when we use autofocus + "noBlankTarget": "off", // Covered by Conformance "useFocusableInteractive": "off", // Disable focusable interactive element requirement "useAriaPropsForRole": "off", // Disable required ARIA attributes check - "noStaticElementInteractions": "warn", - "useValidAutocomplete": "warn", "useKeyWithClickEvents": "off" // Disable keyboard event requirement with click events }, "complexity": { @@ -65,20 +55,20 @@ "noForEach": "off", // forEach is too familiar to ban "noUselessSwitchCase": "off", // Turned off due to developer preferences "noUselessThisAlias": "off", // Turned off due to developer preferences - "noUselessContinue": "off" // Turned off due to developer preferences + "noBannedTypes": "off" }, "correctness": { "noUnusedImports": "warn", // Not in recommended ruleset, turning on manually + "useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually + "noNewSymbol": "warn", // Not in recommended ruleset, turning on manually "useJsxKeyInIterable": "off", // Rule is buggy, revisit later "useExhaustiveDependencies": "warn", // Errors by default, switching to warn instead "noUnnecessaryContinue": "off" // Turned off due to developer preferences }, "security": { - "noDangerouslySetInnerHtml": "off", // Covered by Conformance - "noBlankTarget": "off" // Covered by Conformance + "noDangerouslySetInnerHtml": "off" // Covered by Conformance }, "style": { - "useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually "useFragmentSyntax": "warn", // Not in recommended ruleset, turning on manually "noYodaExpression": "warn", // Not in recommended ruleset, turning on manually "useDefaultParameterLast": "warn", // Not in recommended ruleset, turning on manually @@ -125,20 +115,15 @@ } }, "css": { - "formatter": { - "enabled": false - }, - "linter": { - "enabled": false - } + "formatter": { "enabled": false }, + "linter": { "enabled": false } }, + "organizeImports": { "enabled": false }, "overrides": [ // Playwright requires an object destructure, even if empty // https://github.com/microsoft/playwright/issues/30007 { - "includes": [ - "playwright/**" - ], + "include": ["playwright/**"], "linter": { "rules": { "correctness": { diff --git a/components.json b/components.json index 388ec17744..dde4cacbd1 100644 --- a/components.json +++ b/components.json @@ -1,6 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", + "style": "new-york", "rsc": true, "tsx": true, "tailwind": { @@ -10,11 +10,15 @@ "cssVariables": true, "prefix": "" }, + "iconLibrary": "lucide", "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" + }, + "registries": { + "@ai-elements": "https://registry.ai-sdk.dev/{name}.json" } -} +} \ No newline at end of file diff --git a/components/elements/actions.tsx b/components/elements/actions.tsx index d2d8eb8bb1..0b856b5076 100644 --- a/components/elements/actions.tsx +++ b/components/elements/actions.tsx @@ -35,7 +35,7 @@ export const Action = ({ const button = ( - +
{displayPct} diff --git a/components/elements/prompt-input.tsx b/components/elements/prompt-input.tsx index 19eb2ac6b9..3202386293 100644 --- a/components/elements/prompt-input.tsx +++ b/components/elements/prompt-input.tsx @@ -11,13 +11,19 @@ import { import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; import type { ChatStatus } from 'ai'; -import { Loader2Icon, SendIcon, SquareIcon, XIcon } from 'lucide-react'; +import { Loader2Icon, PlusIcon, SendIcon, SquareIcon, XIcon } from 'lucide-react'; import type { ComponentProps, HTMLAttributes, KeyboardEventHandler, } from 'react'; import { Children } from 'react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; export type PromptInputProps = HTMLAttributes; @@ -31,6 +37,15 @@ export const PromptInput = ({ className, ...props }: PromptInputProps) => ( /> ); +export type PromptInputBodyProps = HTMLAttributes; + +export const PromptInputBody = ({ + className, + ...props +}: PromptInputBodyProps) => ( +
+); + export type PromptInputTextareaProps = ComponentProps & { minHeight?: number; maxHeight?: number; @@ -148,6 +163,49 @@ export const PromptInputButton = ({ ); }; +export type PromptInputActionMenuProps = ComponentProps; +export const PromptInputActionMenu = (props: PromptInputActionMenuProps) => ( + +); + +export type PromptInputActionMenuTriggerProps = ComponentProps< + typeof Button +> & {}; +export const PromptInputActionMenuTrigger = ({ + className, + children, + ...props +}: PromptInputActionMenuTriggerProps) => ( + + + {children ?? } + + +); + +export type PromptInputActionMenuContentProps = ComponentProps< + typeof DropdownMenuContent +>; +export const PromptInputActionMenuContent = ({ + className, + ...props +}: PromptInputActionMenuContentProps) => ( + +); + +export type PromptInputActionMenuItemProps = ComponentProps< + typeof DropdownMenuItem +>; +export const PromptInputActionMenuItem = ({ + className, + ...props +}: PromptInputActionMenuItemProps) => ( + +); + +// Note: Actions that perform side-effects (like opening a file dialog) +// are provided in opt-in modules (e.g., prompt-input-attachments). + export type PromptInputSubmitProps = ComponentProps & { status?: ChatStatus; }; @@ -200,7 +258,7 @@ export const PromptInputModelSelectTrigger = ({ void; + mode: 'view' | 'edit'; }) { const { mutate } = useSWRConfig(); const [_, copyToClipboard] = useCopyToClipboard(); + const [copied, setCopied] = useState(false); if (isLoading) return null; @@ -41,25 +46,40 @@ export function PureMessageActions({ } await copyToClipboard(textFromParts); + setCopied(true); toast.success('Copied to clipboard!'); + setTimeout(() => setCopied(false), 2000); }; // User messages get edit (on hover) and copy actions if (message.role === 'user') { return ( -
+
{setMode && ( - setMode('edit')} - className="-left-10 absolute top-0 opacity-0 transition-opacity group-hover/message:opacity-100" - > + setMode('edit')}> )} - + {copied ? 'Copied' : 'Copy'} + +
@@ -69,7 +89,17 @@ export function PureMessageActions({ return ( - + {copied ? 'Copied' : 'Copy'} + + { if (!equal(prevProps.vote, nextProps.vote)) return false; if (prevProps.isLoading !== nextProps.isLoading) return false; + if (prevProps.mode !== nextProps.mode) return false; return true; }, diff --git a/components/message.tsx b/components/message.tsx index 27c939b2cc..3f8289cfdd 100644 --- a/components/message.tsx +++ b/components/message.tsx @@ -57,7 +57,10 @@ const PurePreviewMessage = ({ return ( )}
diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index ca77889807..65972fd473 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -15,8 +15,7 @@ import { import { toast } from 'sonner'; import { useLocalStorage, useWindowSize } from 'usehooks-ts'; -import { ArrowUpIcon, PaperclipIcon, CpuIcon, StopIcon, ChevronDownIcon } from './icons'; -import { PreviewAttachment } from './preview-attachment'; +import { ArrowUpIcon, CpuIcon, ChevronDownIcon } from './icons'; import { Button } from './ui/button'; import { SuggestedActions } from './suggested-actions'; import { @@ -27,22 +26,36 @@ import { PromptInputSubmit, PromptInputModelSelect, PromptInputModelSelectContent, + PromptInputBody, + PromptInputActionMenu, + PromptInputActionMenuTrigger, + PromptInputActionMenuContent, } from './elements/prompt-input'; import { SelectItem } from '@/components/ui/select'; import * as SelectPrimitive from '@radix-ui/react-select'; import equal from 'fast-deep-equal'; import type { UseChatHelpers } from '@ai-sdk/react'; import { AnimatePresence, motion } from 'framer-motion'; -import { ArrowDown } from 'lucide-react'; +import { + ArrowDown, + ChevronUpIcon, + ImageIcon, + Loader2Icon, + SquareIcon, + TriangleAlertIcon, + XIcon, +} from 'lucide-react'; import { useScrollToBottom } from '@/hooks/use-scroll-to-bottom'; import type { VisibilityType } from './visibility-selector'; import type { Attachment, ChatMessage } from '@/lib/types'; import { chatModels } from '@/lib/ai/models'; import { saveChatModelAsCookie } from '@/app/(chat)/actions'; import { startTransition } from 'react'; -import { getContextWindow, normalizeUsage } from 'tokenlens'; +import { getContextWindow, type ModelId, normalizeUsage } from 'tokenlens'; import { Context } from './elements/context'; import { myProvider } from '@/lib/ai/providers'; +import { PreviewAttachment } from './preview-attachment'; +import { DropdownMenuItem } from './ui/dropdown-menu'; function PureMultimodalInput({ chatId, @@ -124,43 +137,46 @@ function PureMultimodalInput({ const fileInputRef = useRef(null); const [uploadQueue, setUploadQueue] = useState>([]); - const submitForm = useCallback(() => { - window.history.replaceState({}, '', `/chat/${chatId}`); - - sendMessage({ - role: 'user', - parts: [ - ...attachments.map((attachment) => ({ - type: 'file' as const, - url: attachment.url, - name: attachment.name, - mediaType: attachment.contentType, - })), - { - type: 'text', - text: input, - }, - ], - }); - - setAttachments([]); - setLocalStorageInput(''); - resetHeight(); - setInput(''); - - if (width && width > 768) { - textareaRef.current?.focus(); - } - }, [ - input, - setInput, - attachments, - sendMessage, - setAttachments, - setLocalStorageInput, - width, - chatId, - ]); + const submitForm = useCallback( + () => { + window.history.replaceState({}, '', `/chat/${chatId}`); + + sendMessage({ + role: 'user', + parts: [ + ...attachments.map((attachment) => ({ + type: 'file' as const, + url: attachment.url, + name: attachment.name, + mediaType: attachment.contentType, + })), + { + type: 'text', + text: input ?? 'Files were attached', + }, + ], + }); + + setAttachments([]); + setLocalStorageInput(''); + resetHeight(); + setInput(''); + + if (width && width > 768) { + textareaRef.current?.focus(); + } + }, + [ + input, + setInput, + attachments, + sendMessage, + setAttachments, + setLocalStorageInput, + width, + chatId, + ], + ); const uploadFile = async (file: File) => { const formData = new FormData(); @@ -213,7 +229,7 @@ function PureMultimodalInput({ maxTokens: contextMax, usedTokens, usage, - modelId: modelResolver.modelId, + modelId: modelResolver.modelId as ModelId, }), [contextMax, usedTokens, usage, modelResolver], ); @@ -253,7 +269,7 @@ function PureMultimodalInput({ }, [status, scrollToBottom]); return ( -
+
{!isAtBottom && ( + Add photos + ); } -const AttachmentsButton = memo(PureAttachmentsButton); +const ActionAddAttachments = memo(PureActionAddAttachments); function PureModelSelectorCompact({ selectedModelId, @@ -454,19 +494,23 @@ function PureModelSelectorCompact({ > - {selectedModel?.name} - + + {selectedModel?.name} + + {chatModels.map((model) => ( - +
-
- {model.name} -
+
{model.name}
{model.description}
@@ -479,27 +523,3 @@ function PureModelSelectorCompact({ } const ModelSelectorCompact = memo(PureModelSelectorCompact); - -function PureStopButton({ - stop, - setMessages, -}: { - stop: () => void; - setMessages: UseChatHelpers['setMessages']; -}) { - return ( - - ); -} - -const StopButton = memo(PureStopButton); diff --git a/components/preview-attachment.tsx b/components/preview-attachment.tsx index 3df229cfb6..9e2b9cd5a5 100644 --- a/components/preview-attachment.tsx +++ b/components/preview-attachment.tsx @@ -1,8 +1,9 @@ import type { Attachment } from '@/lib/types'; import { Loader } from './elements/loader'; -import { CrossSmallIcon } from './icons'; +import { CrossSmallIcon, PaperclipIcon } from './icons'; import { Button } from './ui/button'; import Image from 'next/image'; +import { XIcon } from 'lucide-react'; export const PreviewAttachment = ({ attachment, @@ -18,39 +19,45 @@ export const PreviewAttachment = ({ const { name, url, contentType } = attachment; return ( -
+
{contentType?.startsWith('image') ? ( - {name ) : ( -
- File +
+
)} {isUploading && ( -
+
)} {onRemove && !isUploading && ( )} -
+
{name}
diff --git a/components/sidebar-history-item.tsx b/components/sidebar-history-item.tsx index e434d8c551..45acf65c37 100644 --- a/components/sidebar-history-item.tsx +++ b/components/sidebar-history-item.tsx @@ -25,6 +25,7 @@ import { } from './icons'; import { memo } from 'react'; import { useChatVisibility } from '@/hooks/use-chat-visibility'; +import { cn } from '@/lib/utils'; const PureChatItem = ({ chat, @@ -44,7 +45,8 @@ const PureChatItem = ({ return ( - + setOpenMobile(false)}> {chat.title} @@ -53,7 +55,7 @@ const PureChatItem = ({ diff --git a/components/suggested-actions.tsx b/components/suggested-actions.tsx index 963ff7b8ad..e5869bf39c 100644 --- a/components/suggested-actions.tsx +++ b/components/suggested-actions.tsx @@ -47,7 +47,7 @@ function PureSuggestedActions({ parts: [{ type: 'text', text: suggestion }], }); }} - className="h-auto w-full whitespace-normal p-3 text-left" + className="h-auto w-full whitespace-normal p-3 text-left rounded-xl" > {suggestedAction} diff --git a/package.json b/package.json index 072f10ff24..6df2248f0c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@ai-sdk/gateway": "^1.0.15", + "@ai-sdk/openai": "^2.0.28", "@ai-sdk/provider": "2.0.0", "@ai-sdk/react": "2.0.26", "@ai-sdk/xai": "2.0.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ce9f4005f..5635ee4a05 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@ai-sdk/gateway': specifier: ^1.0.15 version: 1.0.15(zod@3.25.76) + '@ai-sdk/openai': + specifier: ^2.0.28 + version: 2.0.28(zod@3.25.76) '@ai-sdk/provider': specifier: 2.0.0 version: 2.0.0 @@ -330,12 +333,24 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 + '@ai-sdk/openai@2.0.28': + resolution: {integrity: sha512-Z2mG7PjUKbpT8fMexE6yrorxXVzGHSl3jKF293w2i6s9Dc6X81Gf6Z0OGNnkrftLtW4PXr7RZ/9xoyusBZW4uA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4 + '@ai-sdk/provider-utils@3.0.7': resolution: {integrity: sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 + '@ai-sdk/provider-utils@3.0.8': + resolution: {integrity: sha512-cDj1iigu7MW2tgAQeBzOiLhjHOUM9vENsgh4oAVitek0d//WdgfPCsKO3euP7m7LyO/j9a1vr/So+BGNdpFXYw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4 + '@ai-sdk/provider@2.0.0': resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} @@ -5028,6 +5043,12 @@ snapshots: '@ai-sdk/provider-utils': 3.0.7(zod@3.25.76) zod: 3.25.76 + '@ai-sdk/openai@2.0.28(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.8(zod@3.25.76) + zod: 3.25.76 + '@ai-sdk/provider-utils@3.0.7(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 @@ -5035,6 +5056,13 @@ snapshots: eventsource-parser: 3.0.5 zod: 3.25.76 + '@ai-sdk/provider-utils@3.0.8(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@standard-schema/spec': 1.0.0 + eventsource-parser: 3.0.5 + zod: 3.25.76 + '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 From 0a58f047423f5d157e2ef4d3ea2add64e8e5df10 Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Wed, 10 Sep 2025 16:47:07 +0000 Subject: [PATCH 3/7] chore: remove deprecated configuration and setup files - Deleted devcontainer.json and GitHub action setup files as they are no longer needed. - Updated CI workflow to streamline linting and dependency installation processes. - Refactored AI provider to utilize the new @ai-sdk/openai package for improved model responses. --- .devcontainer/devcontainer.json | 54 -------------------------------- .github/actions/setup/action.yml | 33 ------------------- .github/workflows/lint.yml | 45 +++++++++++--------------- .github/workflows/playwright.yml | 32 +++++++++++++++++-- lib/ai/providers.ts | 10 +++--- 5 files changed, 54 insertions(+), 120 deletions(-) delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .github/actions/setup/action.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 16d07fa054..0000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "ai-chatbot", - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", - "forwardPorts": [ - 3000 - ], - "portsAttributes": { - "3000": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "features": { - "ghcr.io/devcontainers/features/common-utils:2": { - "configureZshAsDefaultShell": true - }, - "ghcr.io/devcontainers/features/github-cli:1": {} - }, - "containerEnv": {}, - "remoteEnv": {}, - "customizations": { - "vscode": { - "extensions": [ - // TypeScript - "better-ts-errors.better-ts-errors", - // Other - "bradlc.vscode-tailwindcss", - "streetsidesoftware.code-spell-checker", - "biomejs.biome", - "aaron-bond.better-comments", - "formulahendry.auto-rename-tag" - ], - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.defaultProfile.linux": "zsh", - "debug.internalConsoleOptions": "neverOpen", - "editor.formatOnPaste": true, - "editor.guides.bracketPairs": "active", - "scm.defaultViewMode": "tree", - "diffEditor.diffAlgorithm": "advanced", - "diffEditor.experimental.showMoves": true, - "diffEditor.renderSideBySide": false, - "files.watcherExclude": { - "**/node_modules/**": true - }, - // Prettifies the response with emojis and such. - "betterTypeScriptErrors.prettify": true - } - } - }, - "postCreateCommand": { - "web": "pnpm install" - } - } \ No newline at end of file diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index 357c47b6d2..0000000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Check setup -description: Set up Node, and pnpm, prime caches, and install dependencies. - -runs: - using: composite - steps: - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - run_install: false - - - name: Get pnpm store path - id: pnpm-cache - shell: bash - run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'pnpm' - - - name: Install dependencies - shell: bash - run: pnpm install --frozen-lockfile \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4046af9860..96a083d3d9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,32 +1,25 @@ -name: CI - +name: Lint on: push: jobs: - types: - name: TypeScript + build: runs-on: ubuntu-22.04 - + strategy: + matrix: + node-version: [20] steps: - - name: Checkout branch - uses: actions/checkout@v4 - - - name: Setup - uses: ./.github/actions/setup - - - name: Run type check - run: pnpm typecheck - - biome: - name: Biome - runs-on: ubuntu-22.04 - steps: - - name: Checkout branch - uses: actions/checkout@v4 - - - name: Setup - uses: ./.github/actions/setup - - - name: Run Biome - run: pnpm check \ No newline at end of file + - uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.12.3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Run lint + run: pnpm lint \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index df6ee65783..3675af9646 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,8 +20,36 @@ jobs: with: fetch-depth: 1 - - name: Setup - uses: ./.github/actions/setup + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: latest + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Cache Playwright browsers uses: actions/cache@v3 diff --git a/lib/ai/providers.ts b/lib/ai/providers.ts index 578fa086f9..67bac375c4 100644 --- a/lib/ai/providers.ts +++ b/lib/ai/providers.ts @@ -3,7 +3,7 @@ import { extractReasoningMiddleware, wrapLanguageModel, } from 'ai'; -import { gateway } from '@ai-sdk/gateway'; +import { openai } from '@ai-sdk/openai'; import { isTestEnvironment } from '../constants'; export const myProvider = isTestEnvironment @@ -25,12 +25,12 @@ export const myProvider = isTestEnvironment })() : customProvider({ languageModels: { - 'chat-model': gateway.languageModel('xai/grok-2-vision-1212'), + 'chat-model': openai.responses('gpt-4o-mini'), 'chat-model-reasoning': wrapLanguageModel({ - model: gateway.languageModel('xai/grok-3-mini'), + model: openai.responses('o4-mini'), middleware: extractReasoningMiddleware({ tagName: 'think' }), }), - 'title-model': gateway.languageModel('xai/grok-2-1212'), - 'artifact-model': gateway.languageModel('xai/grok-2-1212'), + 'title-model': openai('gpt-4o-mini'), + 'artifact-model': openai('gpt-4o-mini'), }, }); From 7c7dcaceac475e3a143a893448949f5f86df0226 Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Wed, 10 Sep 2025 16:47:40 +0000 Subject: [PATCH 4/7] chore: ensure newline at end of lint and playwright workflow files - Added missing newline at the end of the lint.yml and playwright.yml files to adhere to best practices. --- .github/workflows/lint.yml | 2 +- .github/workflows/playwright.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 96a083d3d9..1d57a670b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,4 +22,4 @@ jobs: - name: Install dependencies run: pnpm install - name: Run lint - run: pnpm lint \ No newline at end of file + run: pnpm lint diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3675af9646..6e18c3ec60 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -70,4 +70,4 @@ jobs: with: name: playwright-report path: playwright-report/ - retention-days: 7 \ No newline at end of file + retention-days: 7 From 877f40c0bd050cd094fc1779a890f291f187fb3c Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Wed, 10 Sep 2025 16:49:53 +0000 Subject: [PATCH 5/7] chore: update .gitignore for environment file handling - Added specific local environment files to .gitignore for better clarity. - Removed generic .env* entry to prevent unintentional exclusion of important environment files. - Ensured proper handling of Playwright test results and reports. --- .gitignore | 15 ++++++++------- lib/ai/providers.ts | 12 ++++++------ package.json | 1 - pnpm-lock.yaml | 28 ---------------------------- 4 files changed, 14 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 0750257b79..8140f634bc 100644 --- a/.gitignore +++ b/.gitignore @@ -24,19 +24,20 @@ yarn-error.log* .pnpm-debug.log* # local env files -.env* -!.env.example +.env.local +.env.development.local +.env.test.local +.env.production.local # turbo .turbo + +.env .vercel -.pnpm-store +.env*.local # Playwright /test-results/ /playwright-report/ /blob-report/ -/playwright/* - -# cspell -.cspellcache \ No newline at end of file +/playwright/* \ No newline at end of file diff --git a/lib/ai/providers.ts b/lib/ai/providers.ts index 67bac375c4..e7753f8276 100644 --- a/lib/ai/providers.ts +++ b/lib/ai/providers.ts @@ -3,7 +3,7 @@ import { extractReasoningMiddleware, wrapLanguageModel, } from 'ai'; -import { openai } from '@ai-sdk/openai'; +import { gateway } from '@ai-sdk/gateway'; import { isTestEnvironment } from '../constants'; export const myProvider = isTestEnvironment @@ -25,12 +25,12 @@ export const myProvider = isTestEnvironment })() : customProvider({ languageModels: { - 'chat-model': openai.responses('gpt-4o-mini'), + 'chat-model': gateway.languageModel('xai/grok-2-vision-1212'), 'chat-model-reasoning': wrapLanguageModel({ - model: openai.responses('o4-mini'), + model: gateway.languageModel('xai/grok-3-mini'), middleware: extractReasoningMiddleware({ tagName: 'think' }), }), - 'title-model': openai('gpt-4o-mini'), - 'artifact-model': openai('gpt-4o-mini'), + 'title-model': gateway.languageModel('xai/grok-2-1212'), + 'artifact-model': gateway.languageModel('xai/grok-2-1212'), }, - }); + }); \ No newline at end of file diff --git a/package.json b/package.json index 6df2248f0c..072f10ff24 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ }, "dependencies": { "@ai-sdk/gateway": "^1.0.15", - "@ai-sdk/openai": "^2.0.28", "@ai-sdk/provider": "2.0.0", "@ai-sdk/react": "2.0.26", "@ai-sdk/xai": "2.0.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5635ee4a05..2ce9f4005f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@ai-sdk/gateway': specifier: ^1.0.15 version: 1.0.15(zod@3.25.76) - '@ai-sdk/openai': - specifier: ^2.0.28 - version: 2.0.28(zod@3.25.76) '@ai-sdk/provider': specifier: 2.0.0 version: 2.0.0 @@ -333,24 +330,12 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 - '@ai-sdk/openai@2.0.28': - resolution: {integrity: sha512-Z2mG7PjUKbpT8fMexE6yrorxXVzGHSl3jKF293w2i6s9Dc6X81Gf6Z0OGNnkrftLtW4PXr7RZ/9xoyusBZW4uA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4 - '@ai-sdk/provider-utils@3.0.7': resolution: {integrity: sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 - '@ai-sdk/provider-utils@3.0.8': - resolution: {integrity: sha512-cDj1iigu7MW2tgAQeBzOiLhjHOUM9vENsgh4oAVitek0d//WdgfPCsKO3euP7m7LyO/j9a1vr/So+BGNdpFXYw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4 - '@ai-sdk/provider@2.0.0': resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} @@ -5043,12 +5028,6 @@ snapshots: '@ai-sdk/provider-utils': 3.0.7(zod@3.25.76) zod: 3.25.76 - '@ai-sdk/openai@2.0.28(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.8(zod@3.25.76) - zod: 3.25.76 - '@ai-sdk/provider-utils@3.0.7(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 @@ -5056,13 +5035,6 @@ snapshots: eventsource-parser: 3.0.5 zod: 3.25.76 - '@ai-sdk/provider-utils@3.0.8(zod@3.25.76)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@standard-schema/spec': 1.0.0 - eventsource-parser: 3.0.5 - zod: 3.25.76 - '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 From ffd865e00977be49fdeb9ee956c860399ec389f4 Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Wed, 10 Sep 2025 16:50:45 +0000 Subject: [PATCH 6/7] chore: ensure consistent newline at end of configuration files - Added missing newlines at the end of .gitignore, biome.jsonc, components.json, and providers.ts files to adhere to best practices and improve file consistency. --- .gitignore | 2 +- biome.jsonc | 2 +- components.json | 2 +- lib/ai/providers.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 8140f634bc..54b31f9fcf 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ yarn-error.log* /test-results/ /playwright-report/ /blob-report/ -/playwright/* \ No newline at end of file +/playwright/* diff --git a/biome.jsonc b/biome.jsonc index 36665c0b3f..65729f6292 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -133,4 +133,4 @@ } } ] -} \ No newline at end of file +} diff --git a/components.json b/components.json index dde4cacbd1..085893e85c 100644 --- a/components.json +++ b/components.json @@ -21,4 +21,4 @@ "registries": { "@ai-elements": "https://registry.ai-sdk.dev/{name}.json" } -} \ No newline at end of file +} diff --git a/lib/ai/providers.ts b/lib/ai/providers.ts index e7753f8276..578fa086f9 100644 --- a/lib/ai/providers.ts +++ b/lib/ai/providers.ts @@ -33,4 +33,4 @@ export const myProvider = isTestEnvironment 'title-model': gateway.languageModel('xai/grok-2-1212'), 'artifact-model': gateway.languageModel('xai/grok-2-1212'), }, - }); \ No newline at end of file + }); From 1acc4344a3a1d8b3cdf0436470500e6f5804e53d Mon Sep 17 00:00:00 2001 From: Anirudh Sriram Date: Wed, 10 Sep 2025 17:08:34 +0000 Subject: [PATCH 7/7] fix: update PromptInputActionMenuTrigger styling - Added padding and size classes to the PromptInputActionMenuTrigger component for improved UI consistency. --- components/multimodal-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index a03ce453bb..3abe59c108 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -390,7 +390,7 @@ function PureMultimodalInput({ - +