vercel / ai

Build AI-powered applications with React, Svelte, Vue, and Solid
https://sdk.vercel.ai/docs
Other
9.6k stars 1.41k forks source link

`TypeError: (intermediate value) is not iterable` #1356

Closed abdus closed 2 months ago

abdus commented 5 months ago

Description

I am using Next.js AI Chatbot template. Every time I try to create a new thread by sending a message caused the above-mentioned error. Now, I narrowed down the error using debugger, but still unable to fix it.

Code example

// code that's throwing the error
const [aiStateDelta, result] = await action(
    aiStateSnapshot,
    ...args
);

File: rsc/shared-client/context.tsx

// rsc/shared-client/context.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
var InternalUIStateProvider = React2.createContext(null);
var InternalAIStateProvider = React2.createContext(void 0);
var InternalActionProvider = React2.createContext(null);
var InternalSyncUIStateProvider = React2.createContext(null);
function InternalAIProvider({
  children,
  initialUIState,
  initialAIState,
  initialAIStatePatch,
  wrappedActions,
  wrappedSyncUIState
}) {
  if (!("use" in React2)) {
    throw new Error("Unsupported React version.");
  }
  const uiState = React2.useState(initialUIState);
  const setUIState = uiState[1];
  const resolvedInitialAIStatePatch = initialAIStatePatch ? React2.use(initialAIStatePatch) : void 0;
  initialAIState = React2.useMemo(() => {
    if (resolvedInitialAIStatePatch) {
      return jsondiffpatch.patch(
        jsondiffpatch.clone(initialAIState),
        resolvedInitialAIStatePatch
      );
    }
    return initialAIState;
  }, [initialAIState, resolvedInitialAIStatePatch]);
  const aiState = React2.useState(initialAIState);
  const setAIState = aiState[1];
  const aiStateRef = React2.useRef(aiState[0]);
  React2.useEffect(() => {
    aiStateRef.current = aiState[0];
  }, [aiState[0]]);
  const clientWrappedActions = React2.useMemo(
    () => Object.fromEntries(
      Object.entries(wrappedActions).map(([key, action]) => [
        key,
        async (...args) => {
          const aiStateSnapshot = aiStateRef.current;

          // here
          const [aiStateDelta, result] = await action(
            aiStateSnapshot,
            ...args
          );
          (async () => {
            const delta = await aiStateDelta;
            if (delta !== void 0) {
              aiState[1](
                jsondiffpatch.patch(
                  jsondiffpatch.clone(aiStateSnapshot),
                  delta
                )
              );
            }
          })();
          return result;
        }
      ])
    ),
    [wrappedActions]
  );
  const clientWrappedSyncUIStateAction = React2.useMemo(() => {
    if (!wrappedSyncUIState) {
      return () => {
      };
    }
    return async () => {
      const aiStateSnapshot = aiStateRef.current;
      const [aiStateDelta, uiState2] = await wrappedSyncUIState(
        aiStateSnapshot
      );
      if (uiState2 !== void 0) {
        setUIState(uiState2);
      }
      const delta = await aiStateDelta;
      if (delta !== void 0) {
        const patchedAiState = jsondiffpatch.patch(
          jsondiffpatch.clone(aiStateSnapshot),
          delta
        );
        setAIState(patchedAiState);
      }
    };
  }, [wrappedSyncUIState]);
  return /* @__PURE__ */ jsx2(InternalAIStateProvider.Provider, { value: aiState, children: /* @__PURE__ */ jsx2(InternalUIStateProvider.Provider, { value: uiState, children: /* @__PURE__ */ jsx2(InternalActionProvider.Provider, { value: clientWrappedActions, children: /* @__PURE__ */ jsx2(
    InternalSyncUIStateProvider.Provider,
    {
      value: clientWrappedSyncUIStateAction,
      children
    }
  ) }) }) });
}

My Component Looks like this:

'use client'

import * as React from 'react'
import Textarea from 'react-textarea-autosize'

import { useActions, useUIState } from 'ai/rsc'

import { SpinnerMessage, UserMessage } from './stocks/message'
import { type AI } from '@/lib/chat/actions'
import { Button } from '@/components/ui/button'
import { IconArrowElbow, IconPlus } from '@/components/ui/icons'
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger
} from '@/components/ui/tooltip'
import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
import { nanoid } from 'nanoid'
import { useRouter } from 'next/navigation'
import { getSession } from 'next-auth/react'
import { useChooseGPT } from '@/lib/hooks/use-choose-gpts'
import { createToast } from 'vercel-toast'

export function PromptForm({
  input,
  setInput
}: {
  input: string
  setInput: (value: string) => void
}) {
  const router = useRouter()
  const { gpt } = useChooseGPT()
  const { formRef, onKeyDown } = useEnterSubmit()
  const inputRef = React.useRef<HTMLTextAreaElement>(null)
  const { submitUserMessage } = useActions()
  const [_, setMessages] = useUIState<typeof AI>()

  React.useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  return (
    <form
      ref={formRef}
      onSubmit={async (e: any) => {
        e.preventDefault()

        const session = await getSession()

        if (!session?.user) {
          router.push('/login')
          return
        }

        // Blur focus on mobile
        if (window.innerWidth < 600) {
          e.target['message']?.blur()
        }

        const value = input.trim()

        if (!value) return

        if (!gpt) {
          createToast(
            'Please select an Agent, Role and Task using the Dropdown Menu',
            { type: 'warning', timeout: 5000, cancel: 'Got It' }
          )
          return
        }

        setInput('')

        debugger;

        // Optimistically add user message UI
        setMessages(currentMessages => [
          ...currentMessages,
          {
            id: nanoid(),
            display: <UserMessage>{value}</UserMessage>
          }
        ])

        // also set a loader. remove this from the message state before
        // adding the response
        //
        // WEIRD: if I add the following element and send a message,
        // the chat chat does not change to its own URL
        //
        // adding a timeout to simulate the delay seem to fix the issue
        setTimeout(() => {
          setMessages(currentMessages => [
            ...currentMessages,
            { id: nanoid(), display: <SpinnerMessage /> }
          ])
        }, 300)

        // Submit and get response message
        const responseMessage = await submitUserMessage(value, gpt)

        // remove the loader and add the response
        setMessages(currentMessages => {
          const cloned = [...currentMessages]
          const _spinner = cloned.pop()

          return [...cloned, responseMessage]
        })
      }}
    >
      <div className="relative flex max-h-60 w-full grow flex-col overflow-hidden bg-background px-8 sm:rounded-md sm:border sm:px-12">
        <Tooltip>
          <TooltipTrigger asChild>
            <Button
              variant="outline"
              size="icon"
              className="absolute left-0 top-4 size-8 rounded-full bg-background p-0 sm:left-4"
              onClick={() => {
                router.push('/')
              }}
            >
              <IconPlus />
              <span className="sr-only">New Chat</span>
            </Button>
          </TooltipTrigger>
          <TooltipContent>New Chat</TooltipContent>
        </Tooltip>
        <Textarea
          ref={inputRef}
          tabIndex={0}
          onKeyDown={onKeyDown}
          placeholder="Send a message."
          className="min-h-[60px] w-full resize-none bg-transparent px-4 py-[1.3rem] focus-within:outline-none sm:text-sm"
          autoFocus
          spellCheck={false}
          autoComplete="off"
          autoCorrect="off"
          name="message"
          rows={1}
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <div className="absolute right-0 top-4 sm:right-4">
          <Tooltip>
            <TooltipTrigger asChild>
              <Button type="submit" size="icon" disabled={input === ''}>
                <IconArrowElbow />
                <span className="sr-only">Send message</span>
              </Button>
            </TooltipTrigger>
            <TooltipContent>Send message</TooltipContent>
          </Tooltip>
        </div>
      </div>
    </form>
  )
}

Application-level Error Message:

Unhandled Runtime Error
TypeError: (intermediate value) is not iterable

Source
components/prompt-form.tsx (116:32) @ async onSubmit

  114 |
  115 | // Submit and get response message
> 116 | const responseMessage = await submitUserMessage(value, gpt)
      |                        ^
  117 |
  118 | // remove the loader and add the response
  119 | setMessages(currentMessages => {

Additional context

I have been trying to fix this issue since last week. Any help is appreciated. Thank you in advance.

audvin commented 5 months ago

I'm seeing the same error on Vercel, but not on local.

My temp bandaid is to auto retry when that failure happens. The user will only experience a little delay.

        // Submit and get response message
        let attempts = 0
        let responseMessage: any = null
        while (attempts < 3) {
          try {
            responseMessage = await submitUserMessage(value)
            if (responseMessage) {
              setMessages(currentMessages => [...currentMessages, responseMessage])
            }
            break // Exit loop on success
          } catch (error) {
            console.error(`Attempt ${attempts + 1} failed:`, error)
            attempts++
            if (attempts === 3) {
              console.error('Failed to submit user message after 3 attempts.')
            }
          }
        }
SergioAndesian commented 4 months ago

were you able to solve this? I'm having the same problem.

Shervlock-OPG commented 4 months ago

This is pretty late but I was having the same issue as I was migrating a project to RSCs and managed to fix it.

The problem for me was the auth check in the middleware, to redirect to the login page if the request came from a user that wasn't logged in, and that was conflicting with the server action request, causing the error.

Fixed it by adding a check to see if the "Accept" field in the header is for a component.

import { auth } from '@/auth'
import { NextResponse } from 'next/server'

// More on how NextAuth.js middleware works: https://authjs.dev/getting-started/migrating-to-v5#details
export default auth(req => {
  const url = new URL('/login', req.url)

  const acceptHeader = req.headers.get('accept')

  // Allow requests for Server Actions and Server Components
  if (acceptHeader && acceptHeader.includes('text/x-component')) {
    return NextResponse.next()
  }

  // Redirect to login page if user is not authenticated
  if (!req.auth?.user) {
    return !req.nextUrl.pathname.includes('/login') // Prevent infinite redirect loop
      ? NextResponse.redirect(url)
      : NextResponse.next()
  }

  return NextResponse.next()
})

export const config = {
  matcher: ['/((?!api|_next/static|favicon|_next/image||.*\\.png$).*)']
}
sylviangth commented 4 months ago

Any update on this? Today I run my repo in local and encountered this exact issue and couldn't figure out how to debug it. A couple days before it was still fine. On production it's still fine. But for some reason, the issue suddenly appeared. Any help please?

nicoalbanese commented 3 months ago

Hey! Did you by chance update all of the dependencies? Can you check what version of next-auth you are using?

I tried to reproduce and got this error after running pnpm up --latest. This command ended up downgrading my next-auth version from 5.0.0-beta.4 to 4.24.7. I ran pnpm install next-auth@beta and it resolved all the issues.

Let me know if that resolves the problem for you.

MaxLeiter commented 2 months ago

Please report back if this is still an issue

Daniel-Ash commented 1 month ago

If you're looking into this issue, you might find this helpful:

https://github.com/vercel/next.js/discussions/64993

It seems like middleware redirects have been disabled for server actions - so if you want to authenticate a server action, you'll need to do that inside the action.