Modify the local bolt.new interface to allow input of the API key

m_sea_bass

M Sea Bass

Posted on November 17, 2024

Modify the local bolt.new interface to allow input of the API key

In bolt.new, the API key can be configured using environment variables, but this time, we will modify it to allow input of the API key directly from the interface.

Modification Details

Sidebar

We will enable API key input directly from the sidebar.

In the sidebar, which currently displays chat history, we add a new form at the top for entering the API key.

To achieve this, modify the file bolt.new/app/components/sidebar/Menu.client.tsx.

First, import the function to handle API key input:

import { ApiKeyInput } from '~/components/sidebar/ApiKeyInput';  
Enter fullscreen mode Exit fullscreen mode

The bolt.new/app/components/sidebar/ApiKeyInput.tsx file will be created later.

Next, add a form for entering the API key within the menu.

...  
  return (  
      <motion.div  
        ref={menuRef}  
        initial="closed"  
        animate={open ? 'open' : 'closed'}  
        variants={menuVariants}  
        className="flex flex-col side-menu fixed top-0 w-[350px] h-full bg-bolt-elements-background-depth-2 border-r rounded-r-3xl border-bolt-elements-borderColor z-sidebar shadow-xl shadow-bolt-elements-sidebar-dropdownShadow text-sm"  
      >  
        <div className="flex items-center h-[var(--header-height)]">{/* Placeholder */}</div>  
        <div className="flex-1 flex flex-col h-full w-full overflow-hidden">  
          <ApiKeyInput /> {/* Add this line */}  
          <div className="p-4">  
...  
Enter fullscreen mode Exit fullscreen mode

The added code should be placed here.

Next, create the file bolt.new/app/components/sidebar/ApiKeyInput.tsx with the following content:

import React, { useState } from 'react';  

export function ApiKeyInput() {  
  const [apiKey, setApiKey] = useState(localStorage.getItem('apiKey') || '');  

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {  
    const value = event.target.value;  
    setApiKey(value);  
    localStorage.setItem('apiKey', value);  
    // Trigger API key change event  
    window.dispatchEvent(new Event('apiKeyChanged'));  
  };  

  return (  
    <div className="px-4 py-3 border-b border-bolt-elements-borderColor">  
      <label   
        htmlFor="api-key"   
        className="block text-bolt-elements-textSecondary text-sm mb-2"  
      >  
        Anthropic API Key  
      </label>  
      <input  
        type="password"  
        id="api-key"  
        value={apiKey}  
        onChange={handleChange}  
        placeholder="sk-..."  
        className="w-full px-3 py-2 bg-bolt-elements-background-depth-1  
                   border border-bolt-elements-borderColor  
                   rounded-md text-bolt-elements-textPrimary  
                   placeholder:text-bolt-elements-textTertiary  
                   focus:outline-none focus:ring-1 focus:ring-bolt-elements-focusRing  
                   transition-all duration-200"  
      />  
    </div>  
  );  
}  
Enter fullscreen mode Exit fullscreen mode

This component will allow the user to input and store the API key in localStorage and trigger a custom event when the key is changed.

Chat Screen Modification

Update the chat screen to disable message sending until an API key is entered.

Below is the revised code for bolt.new/app/components/chat/BaseChat.client.tsx, with additions marked between // Append start and // Append end:

export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(  
  (  
    {  
      textareaRef,  
      messageRef,  
      scrollRef,  
      showChat = true,  
      chatStarted = false,  
      isStreaming = false,  
      enhancingPrompt = false,  
      promptEnhanced = false,  
      messages,  
      input = '',  
      sendMessage,  
      handleInputChange,  
      enhancePrompt,  
      handleStop,  
    },  
    ref,  
  ) => {  
    // Append start  
    const [isApiKeyMissing, setIsApiKeyMissing] = useState(true); // Track API key presence  

    useEffect(() => {  
      const checkApiKey = () => {  
        const apiKey = localStorage.getItem('apiKey');  
        console.log('apiKey:', apiKey);  
        setIsApiKeyMissing(!apiKey);  
      };  

      // Initial check  
      checkApiKey();  

      // Add listener for API key changes  
      window.addEventListener('apiKeyChanged', checkApiKey);  

      return () => {  
        window.removeEventListener('apiKeyChanged', checkApiKey);  
      };  
    }, []);  
    // Append end  

    const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;  

    return (  
      <div  
        ref={ref}  
        className={classNames(  
          styles.BaseChat,  
          'relative flex h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',  
        )}  
        data-chat-visible={showChat}  
      >  
        <ClientOnly>{() => <Menu />}</ClientOnly>  
        <div ref={scrollRef} className="flex overflow-y-auto w-full h-full">  
          <div className={classNames(styles.Chat, 'flex flex-col flex-grow min-w-[var(--chat-min-width)] h-full')}>  
            {!chatStarted && (  
              <div id="intro" className="mt-[26vh] max-w-chat mx-auto">  
                <h1 className="text-5xl text-center font-bold text-bolt-elements-textPrimary mb-2">  
                  Where ideas begin  
                </h1>  
                <p className="mb-4 text-center text-bolt-elements-textSecondary">  
                  Bring ideas to life in seconds or get help on existing projects.  
                </p>  
              </div>  
            )}  
            <div  
              className={classNames('pt-6 px-6', {  
                'h-full flex flex-col': chatStarted,  
              })}  
            >  
              <ClientOnly>  
                {() => {  
                  return chatStarted ? (  
                    <Messages  
                      ref={messageRef}  
                      className="flex flex-col w-full flex-1 max-w-chat px-4 pb-6 mx-auto z-1"  
                      messages={messages}  
                      isStreaming={isStreaming}  
                    />  
                  ) : null;  
                }}  
              </ClientOnly>  
              <div  
                className={classNames('relative w-full max-w-chat mx-auto z-prompt', {  
                  'sticky bottom-0': chatStarted,  
                })}  
              >  
                <div  
                  className={classNames(  
                    'shadow-sm border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden',  
                  )}  
                >  
                  {/* Append start */}  
                  {isApiKeyMissing && (  
                    <div className="px-4 py-2 bg-red-500/10 text-red-400 text-sm">  
                      Please Specify the Anthropic API Key in the Sidebar.  
                    </div>  
                  )}  
                  {/* Append end */}  
                  <textarea  
                    ref={textareaRef}  
                    className={`w-full pl-4 pt-4 pr-16 focus:outline-none resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent ${isApiKeyMissing ? 'cursor-not-allowed opacity-50' : ''}`}  
                    onKeyDown={(event) => {  
                      if (event.key === 'Enter') {  
                        if (event.shiftKey) {  
                          return;  
                        }  

                        event.preventDefault();  

                        sendMessage?.(event);  
                      }  
                    }}  
                    value={input}  
                    onChange={(event) => {  
                      handleInputChange?.(event);  
                    }}  
                    style={{  
                      minHeight: TEXTAREA_MIN_HEIGHT,  
                      maxHeight: TEXTAREA_MAX_HEIGHT,  
                    }}  
                    placeholder="How can Bolt help you today?"  
                    translate="no"  
                    // Append start  
                    disabled={isApiKeyMissing}  
                    // Append end  
                  />  
...  
Enter fullscreen mode Exit fullscreen mode

This ensures that users cannot send messages until they enter an API key, with clear visual feedback provided.

Passing the API Key to the LLM

To ensure the API key entered on the interface is accessible to the LLM, update the file bolt.new/app/lib/.server/llm/api-key.ts as follows:

import { env } from 'node:process';

export function getAPIKey(cloudflareEnv: Env) {  
  // Append start  
  const localApiKey = typeof window !== 'undefined' ? localStorage.getItem('apiKey') : null;  
  return localApiKey || env.ANTHROPIC_API_KEY || cloudflareEnv.ANTHROPIC_API_KEY;  
  // Append end  
}  
Enter fullscreen mode Exit fullscreen mode

This ensures that the system prioritizes the API key entered via the UI (localApiKey). If no key is found in localStorage, it will fall back to environment variables (env.ANTHROPIC_API_KEY or cloudflareEnv.ANTHROPIC_API_KEY).

Testing the Implementation

After completing the modifications, use the following commands to build and start bolt.new:

pnpm run build  
pnpm run start  
Enter fullscreen mode Exit fullscreen mode

Steps for Verification

  1. Launch the application in the browser Confirm that before entering an API key, the message input is disabled, and a warning is displayed.

Image description

  1. Enter the API key Use the sidebar form to input the API key.

Image description

  1. Verify that messages can be sent After entering the API key, confirm that the message input is enabled and messages can be sent successfully.

Image description

These steps ensure that the functionality behaves as intended after the modifications.

💖 💪 🙅 🚩
m_sea_bass
M Sea Bass

Posted on November 17, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related