> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinecone.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pinecone Assistant

> Sample Next.js chat app that connects to a Pinecone Assistant for document-grounded answers with citations and file references.

export const ArrowNE = () => {
  return <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none">
            <path d="M4.92299 17.1668L3.8335 16.0773L14.5209 5.38992H4.71547V3.8335H17.1668V16.2849H15.6104V6.47941L4.92299 17.1668Z" fill="var(--text-primary)" />
        </svg>;
};

export const GithubIcon = () => {
  return <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none">
            <path fill-rule="evenodd" clip-rule="evenodd" d="M9.70928 4.05007C8.31793 4.21017 6.96422 4.84637 5.94277 5.82016C4.24747 7.43636 3.52414 9.77037 4.00655 12.0678C4.46605 14.2561 6.02227 16.0844 8.11272 16.892C8.44405 17.0199 8.60214 17.0277 8.72515 16.9219L8.81336 16.8461L8.82292 16.1332L8.83247 15.4202L8.73955 15.4406C8.50614 15.4919 8.10282 15.5127 7.86284 15.4858C7.40903 15.435 7.01153 15.2388 6.79946 14.9611C6.73983 14.883 6.61481 14.6617 6.52166 14.4695C6.33116 14.0762 6.15107 13.8536 5.86758 13.6611C5.5353 13.4355 5.54995 13.2709 5.90191 13.2757C6.03736 13.2776 6.13771 13.3031 6.28618 13.3737C6.55388 13.5008 6.72474 13.6554 6.91734 13.9445C7.19338 14.3588 7.39605 14.5261 7.74282 14.6258C7.98116 14.6943 8.38716 14.6732 8.66027 14.5782L8.85297 14.5111L8.88852 14.332C8.93357 14.1048 9.04413 13.8664 9.17182 13.721C9.2859 13.591 9.28559 13.5908 8.97604 13.5556C8.70198 13.5243 8.21426 13.399 7.95469 13.2931C7.5852 13.1423 7.38567 13.008 7.08345 12.7068C6.57847 12.2034 6.3554 11.644 6.28281 10.6989C6.22063 9.88945 6.37347 9.29167 6.78619 8.73013L6.93636 8.5258L6.87447 8.29003C6.79346 7.98149 6.80996 7.41637 6.91027 7.06404C6.99339 6.77209 7.03036 6.74053 7.2572 6.76816C7.62556 6.81299 8.05296 6.98479 8.54616 7.28629L8.81336 7.44963L9.14344 7.38346C9.73562 7.26472 10.0045 7.24354 10.6681 7.26331C11.2515 7.28069 11.4102 7.29927 11.9297 7.4109L12.1225 7.45233L12.3541 7.30892C12.8263 7.01647 13.2498 6.83572 13.5901 6.78137C13.9054 6.73098 13.9339 6.74657 14.0112 7.01148C14.1466 7.47534 14.1575 8.05323 14.0375 8.40003C13.9956 8.52102 13.9958 8.52159 14.1525 8.72787C14.3401 8.97477 14.5238 9.35728 14.5959 9.65114C14.7536 10.2933 14.6496 11.354 14.3681 11.9748C14.0175 12.7481 13.4125 13.2118 12.4285 13.4617C12.2729 13.5012 12.0607 13.5432 11.9569 13.555C11.6405 13.5909 11.6372 13.5933 11.7462 13.708C11.848 13.8152 11.973 14.0466 12.0373 14.2471C12.0657 14.336 12.0829 14.7354 12.0959 15.6095L12.1141 16.8461L12.2023 16.9219C12.3253 17.0277 12.4834 17.0199 12.8148 16.892C15.1496 15.99 16.7734 13.8552 17.0364 11.3417C17.0826 10.9001 17.0576 9.99571 16.9878 9.5789C16.6755 7.71598 15.6173 6.10421 14.016 5.05281C13.2185 4.52921 12.1702 4.15856 11.171 4.0469C10.8383 4.00971 10.0449 4.01144 9.70928 4.05007Z" fill="var(--text-primary)" />
        </svg>;
};

export const InlineCode = ({copyCode, displayCode, copy}) => {
  const copyToClipboard = async e => {
    await navigator.clipboard.writeText(copyCode);
    const button = e.target.closest("button");
    button.innerHTML = `<svg  width="16" height="11" viewBox="0 0 16 11" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform: translateY(-3px)"><path d="M14.7813 1.21873C15.0751 1.51248 15.0751 1.98748 14.7813 2.2781L6.53135 10.5312C6.2376 10.825 5.7626 10.825 5.47197 10.5312L1.21885 6.28123C0.925098 5.98748 0.925098 5.51248 1.21885 5.22185C1.5126 4.93123 1.9876 4.9281 2.27822 5.22185L5.99697 8.9406L13.7188 1.21873C14.0126 0.924976 14.4876 0.924976 14.7782 1.21873H14.7813Z" fill="var(--brand-blue)"></path></svg>`;
    setTimeout(() => {
      button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
      <path d="M14.2502 0.833496H4.25016C3.3335 0.833496 2.5835 1.5835 2.5835 2.50016V14.1668H4.25016V2.50016H14.2502V0.833496ZM16.7502 4.16683H7.5835C6.66683 4.16683 5.91683 4.91683 5.91683 5.8335V17.5002C5.91683 18.4168 6.66683 19.1668 7.5835 19.1668H16.7502C17.6668 19.1668 18.4168 18.4168 18.4168 17.5002V5.8335C18.4168 4.91683 17.6668 4.16683 16.7502 4.16683ZM16.7502 17.5002H7.5835V5.8335H16.7502V17.5002Z" fill="var(--brand-blue)" fill-opacity="0.38"/>
      </svg>`;
    }, 2000);
  };
  return <div className="relative">
      <code className="inline-flex gap-2 items-center py-2 pl-3 pr-10 custom-code">
        {displayCode}
      </code>

      {copy && <div className="absolute group right-3 top-1/2 -translate-y-1/2" style={{
    width: "1.375rem",
    height: "1.375rem"
  }}>
          <button onClick={e => copyToClipboard(e)}>
            <svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
              <path d="M14.2502 0.833496H4.25016C3.3335 0.833496 2.5835 1.5835 2.5835 2.50016V14.1668H4.25016V2.50016H14.2502V0.833496ZM16.7502 4.16683H7.5835C6.66683 4.16683 5.91683 4.91683 5.91683 5.8335V17.5002C5.91683 18.4168 6.66683 19.1668 7.5835 19.1668H16.7502C17.6668 19.1668 18.4168 18.4168 18.4168 17.5002V5.8335C18.4168 4.91683 17.6668 4.16683 16.7502 4.16683ZM16.7502 17.5002H7.5835V5.8335H16.7502V17.5002Z" fill="var(--brand-blue)" className="opacity-40 group-hover:opacity-100 transition-opacity" />
            </svg>
          </button>
        </div>}
    </div>;
};

<div className="sample-app">
  <div className="sample-app-heading">
    <span className="eyebrow">SAMPLE APP</span>

    # Pinecone Assistant

    A chat interface for your Pinecone Assistant to answer complex questions on your proprietary data

    <InlineCode copyCode="npx create-pinecone-app@latest --template pinecone-assistant" displayCode={<span><span style={{color: "#215CCE"}}>$</span> npx create-pinecone-app@latest --template pinecone-assistant</span>} copy />
  </div>

  <div className="w-full h-fit rounded-lg " style={{background: "#121142", margin: "4rem 0 3rem"}}>
    <div className="container py-50">
      <iframe id="sample-app-iframe" className=" border-[0.5px] border-gray-300 rounded-lg shadow-xl" src="https://pinecone-assistant.showcase.pinecone.io/" width="100%" height="1100px" allow="clipboard-write" allowTransparency="true" />
    </div>
  </div>

  <div className="sample-app-split">
    <div className="content no-margin">
      The Pinecone Assistant sample app demonstrates how to connect a chat interface to your Pinecone Assistant to answer complex questions on your proprietary data. This app allows users to upload PDF documents, process them, and then ask questions about the content using a chat interface.
    </div>

    <div className="sidebar">
      <InlineCode copyCode="npx create-pinecone-app --template pinecone-assistant" displayCode={<span><span style={{color: "#215CCE"}}>$</span> npx create-pinecone-app@latest --template pinecone-assistant</span>} copy />

      <a href="https://github.com/pinecone-io/sample-apps/tree/main/pinecone-assistant" target="_blank" className="flex items-center gap-2 no-underline mt-6"><GithubIcon /> Github</a>
      <a href="https://pinecone-assistant.showcase.pinecone.io/" target="_blank" className="flex items-center gap-2 no-underline mt-4"><ArrowNE /> Open in a new window</a>
    </div>
  </div>

  ***

  <div className="sample-app-split">
    <div className="content relative mt-8 prose prose-gray dark:prose-invert">
      ## Built with

      * Pinecone Assistant API
      * Next.js + tailwind
      * Node version 20 or higher

      ***

      ## Run the sample app

      The fastest way to get started is to use the `create-pinecone-app` CLI tool to get up and running:

      ```bash theme={null}
      npx -y create-pinecone-app@latest --template pinecone-assistant
      ```

      ### Get your API key

      You need an API key to make API calls to your Pinecone project:

      <div style={{minWidth: '450px', minHeight:'152px'}}>
        <div id="pinecone-connect-widget">
          <div class="connect-widget-skeleton">
            <div class="skeleton-content" />
          </div>
        </div>
      </div>

      Then copy your generated key:

      ```
      PINECONE_API_KEY="{{YOUR_API_KEY}}"
      ```

      Alternatively, follow these steps:

      1. Open the Pinecone console.
      2. Select your project.
      3. Go to **[API Keys](https://app.pinecone.io/organizations/-/projects/-/keys)**.
      4. Copy your API key.

      ### Create a Pinecone Assistant

      You can create a Pinecone Assistant [in the console](https://app.pinecone.io/organizations/-/projects/-/assistant), or by following the instructions [here](/guides/assistant/create-assistant).

      ### Start the project

      **Requires Node version 20+**

      #### Dependency installation

      From the project root directory, run the following command:

      ```bash theme={null}
      cd pinecone-assistant && npm install
      ```

      Make sure you have populated the `.env` file with relevant keys:

      ```bash theme={null}
      PINECONE_API_KEY="your-pinecone-api-key-here"
      PINECONE_ASSISTANT_NAME="your-pinecone-assistant-name-here"
      # Set this if you want users chatting with your assistant to be able to see
      # and click into the files used as references in answers
      SHOW_ASSISTANT_FILES=true
      ```

      Start the app:

      ```bash theme={null}
      npm run dev
      ```

      ## Project structure

      This project uses a standard Next.js application structure with API routes for backend functionality.

      **Frontend client**

      The frontend uses Next.js, Tailwind CSS, and custom React components to power the chat interface.

      **Backend server**

      This project uses Next.js API routes to proxy requests to the Pinecone Assistant API.

      <img src="https://mintcdn.com/pinecone/r0TaYXrfSrAYZYUj/images/pinecone-assistant-architecture.png?fit=max&auto=format&n=r0TaYXrfSrAYZYUj&q=85&s=2ed994fe84cb23028b0088be907348ea" alt="Pinecone Assistant" width="2000" height="1737" data-path="images/pinecone-assistant-architecture.png" />

      ***

      ### Key features

      1. **Connect to existing Pinecone Assistant**: Connect to an existing Pinecone Assistant to provide a chat experience that can be hosted privately or publicly.

      2. **Streaming responses**: Ask questions of the assistant and get responses streamed to the frontend in real-time.

      3. **Reference highlighting**: Documents that were used in answering user questions are highlighted as references.

      ### Implementation details

      **Server action for chat**

      The server action creates a stream with Pinecone Assistants:

      ```typescript theme={null}
      'use server'

      import { createStreamableValue } from 'ai/rsc'
      import { EventSource } from 'extended-eventsource';

      type Message = {
        role: string;
        content: string;
      }

      export async function chat(messages: Message[]) {

        // Create an initial stream, which we'll populate with events from the Pinecone Assistants API
        const stream = createStreamableValue()

        // Construct the full URL to the Pinecone Assistant API for the specific assistant
        // indicated by the user
        const url = `${process.env.PINECONE_ASSISTANT_URL}/${process.env.PINECONE_ASSISTANT_NAME}/chat/completions`

        const eventSource = new EventSource(url, {
          method: 'POST',
          body: JSON.stringify({
            stream: true,
            messages,
          }),
          headers: {
            Authorization: `Bearer ${process.env.PINECONE_API_KEY}`,
            'X-Project-Id': process.env.PINECONE_ASSISTANT_ID!,
          },
          disableRetry: true,
        });

        // When we receive a new message from the Pinecone Assistant API, we update the stream
        // unless the Assistant is done, in which case we close the stream
        eventSource.onmessage = (event: MessageEvent) => {
          const message = JSON.parse(event.data)
          if (message?.choices[0]?.finish_reason) {
            eventSource.close();
            stream.done();
          } else {
            stream.update(event.data)
          }
        };

        eventSource.onerror = (error) => {
          console.error('EventSource error:', error);
          eventSource.close();
        };

        return { object: stream.value }
      }
      ```

      **Chat functionality**

      The chat functionality in the Home component consumes the stream from the server action and updates the UI in real-time:

      ```typescript theme={null}
      const handleChat = async () => {
          if (!input.trim()) return;

          const newUserMessage: Message = {
            id: uuidv4(), // Generate a unique ID
            role: 'user',
            content: input,
            timestamp: new Date().toISOString()
          };

          setMessages(prevMessages => [...prevMessages, newUserMessage]);
          setInput('');
          setIsStreaming(true);

          try {
            const { object } = await chat([newUserMessage]);
            let accumulatedContent = '';
            const newAssistantMessage: Message = {
              id: uuidv4(),
              role: 'assistant',
              content: '',
              timestamp: new Date().toISOString(),
              references: []
            };

            setMessages(prevMessages => [...prevMessages, newAssistantMessage]);

            // Process the response stream from the Assistant that is created in the ./actions.ts Server action
            for await (const chunk of readStreamableValue(object)) {
              try {
                const data = JSON.parse(chunk);
                const content = data.choices[0]?.delta?.content;

                if (content) {
                  accumulatedContent += content;
                }

                setMessages(prevMessages => {
                  const updatedMessages = [...prevMessages];
                  const lastMessage = updatedMessages[updatedMessages.length - 1];
                  lastMessage.content = accumulatedContent;
                  return updatedMessages;
                });

              } catch (error) {
                console.error('Error parsing chunk:', error);
              }
            }

            // Extract references after the full message is received
            const extractedReferences = extractReferences(accumulatedContent);
            setReferencedFiles(extractedReferences);

          } catch (error) {
            console.error('Error in chat:', error);
            setError('An error occurred while chatting.');
          } finally {
            setIsStreaming(false);
          }
        };
      ```

      ***

      ## Troubleshooting

      Experiencing any issues with the sample app?
      [Submit an issue, create a PR](https://github.com/pinecone-io/sample-apps/), or post in our [community forum](https://community.pinecone.io)!
    </div>

    <div className="sidebar toc" />
  </div>
</div>
