Skip to content

Commit

Permalink
feat: add ability to enter API keys in the UI
Browse files Browse the repository at this point in the history
  • Loading branch information
ali00209 committed Oct 27, 2024
1 parent 8e7220e commit 3c7bf8c
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 17 deletions.
49 changes: 49 additions & 0 deletions app/components/chat/APIKeyManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useState } from 'react';
import { IconButton } from '~/components/ui/IconButton';

interface APIKeyManagerProps {
provider: string;
apiKey: string;
setApiKey: (key: string) => void;
}

export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey, setApiKey }) => {
const [isEditing, setIsEditing] = useState(false);
const [tempKey, setTempKey] = useState(apiKey);

const handleSave = () => {
setApiKey(tempKey);
setIsEditing(false);
};

return (
<div className="flex items-center gap-2 mt-2 mb-2">
<span className="text-sm text-bolt-elements-textSecondary">{provider} API Key:</span>
{isEditing ? (
<>
<input
type="password"
value={tempKey}
onChange={(e) => setTempKey(e.target.value)}
className="flex-1 p-1 text-sm rounded border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus"
/>
<IconButton onClick={handleSave} title="Save API Key">
<div className="i-ph:check" />
</IconButton>
<IconButton onClick={() => setIsEditing(false)} title="Cancel">
<div className="i-ph:x" />
</IconButton>
</>
) : (
<>
<span className="flex-1 text-sm text-bolt-elements-textPrimary">
{apiKey ? '••••••••' : 'Not set'}
</span>
<IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
<div className="i-ph:pencil-simple" />
</IconButton>
</>
)}
</div>
);
};
58 changes: 41 additions & 17 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-nocheck
// Preventing TS checks with files presented in the video for a better presentation.
import type { Message } from 'ai';
import React, { type RefCallback } from 'react';
import React, { type RefCallback, useEffect } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { Menu } from '~/components/sidebar/Menu.client';
import { IconButton } from '~/components/ui/IconButton';
Expand All @@ -11,6 +11,7 @@ import { MODEL_LIST, DEFAULT_PROVIDER } from '~/utils/constants';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import { useState } from 'react';
import { APIKeyManager } from './APIKeyManager';

import styles from './BaseChat.module.scss';

Expand All @@ -24,18 +25,17 @@ const EXAMPLE_PROMPTS = [

const providerList = [...new Set(MODEL_LIST.map((model) => model.provider))]

const ModelSelector = ({ model, setModel, modelList, providerList }) => {
const [provider, setProvider] = useState(DEFAULT_PROVIDER);
const ModelSelector = ({ model, setModel, modelList, providerList, provider, setProvider }) => {
return (
<div className="mb-2">
<select
<div className="mb-2 flex gap-2">
<select
value={provider}
onChange={(e) => {
setProvider(e.target.value);
const firstModel = [...modelList].find(m => m.provider == e.target.value);
setModel(firstModel ? firstModel.name : '');
}}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all"
>
{providerList.map((provider) => (
<option key={provider} value={provider}>
Expand All @@ -52,7 +52,7 @@ const ModelSelector = ({ model, setModel, modelList, providerList }) => {
<select
value={model}
onChange={(e) => setModel(e.target.value)}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all"
>
{[...modelList].filter(e => e.provider == provider && e.name).map((modelOption) => (
<option key={modelOption.name} value={modelOption.name}>
Expand Down Expand Up @@ -108,6 +108,23 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
ref,
) => {
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
const [provider, setProvider] = useState(DEFAULT_PROVIDER);
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});

useEffect(() => {
// Load API keys from localStorage on component mount
const storedApiKeys = localStorage.getItem('apiKeys');
if (storedApiKeys) {
setApiKeys(JSON.parse(storedApiKeys));
}
}, []);

const updateApiKey = (provider: string, key: string) => {
const updatedApiKeys = { ...apiKeys, [provider]: key };
setApiKeys(updatedApiKeys);
// Save updated API keys to localStorage
localStorage.setItem('apiKeys', JSON.stringify(updatedApiKeys));
};

return (
<div
Expand All @@ -122,11 +139,11 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
<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">
<div id="intro" className="mt-[26vh] max-w-chat mx-auto text-center">
<h1 className="text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
Where ideas begin
</h1>
<p className="mb-4 text-center text-bolt-elements-textSecondary">
<p className="text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
Bring ideas to life in seconds or get help on existing projects.
</p>
</div>
Expand Down Expand Up @@ -158,15 +175,22 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
setModel={setModel}
modelList={MODEL_LIST}
providerList={providerList}
provider={provider}
setProvider={setProvider}
/>
<APIKeyManager
provider={provider}
apiKey={apiKeys[provider] || ''}
setApiKey={(key) => updateApiKey(provider, key)}
/>
<div
className={classNames(
'shadow-sm border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden',
'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
)}
>
<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`}
className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent transition-all`}
onKeyDown={(event) => {
if (event.key === 'Enter') {
if (event.shiftKey) {
Expand Down Expand Up @@ -205,12 +229,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
/>
)}
</ClientOnly>
<div className="flex justify-between text-sm p-4 pt-2">
<div className="flex justify-between items-center text-sm p-4 pt-2">
<div className="flex gap-1 items-center">
<IconButton
title="Enhance prompt"
disabled={input.length === 0 || enhancingPrompt}
className={classNames({
className={classNames('transition-all', {
'opacity-100!': enhancingPrompt,
'text-bolt-elements-item-contentAccent! pr-1.5 enabled:hover:bg-bolt-elements-item-backgroundAccent!':
promptEnhanced,
Expand All @@ -219,7 +243,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
>
{enhancingPrompt ? (
<>
<div className="i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress text-xl"></div>
<div className="i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress text-xl animate-spin"></div>
<div className="ml-1.5">Enhancing prompt...</div>
</>
) : (
Expand All @@ -232,7 +256,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
</div>
{input.length > 3 ? (
<div className="text-xs text-bolt-elements-textTertiary">
Use <kbd className="kdb">Shift</kbd> + <kbd className="kdb">Return</kbd> for a new line
Use <kbd className="kdb px-1.5 py-0.5 rounded bg-bolt-elements-background-depth-2">Shift</kbd> + <kbd className="kdb px-1.5 py-0.5 rounded bg-bolt-elements-background-depth-2">Return</kbd> for a new line
</div>
) : null}
</div>
Expand Down Expand Up @@ -266,4 +290,4 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
</div>
);
},
);
);

0 comments on commit 3c7bf8c

Please sign in to comment.