Skip to content

Commit

Permalink
Merge remote-tracking branch 'coleam00/main' into import-export-indiv…
Browse files Browse the repository at this point in the history
…idual-chats

# Conflicts:
#	app/components/chat/BaseChat.tsx
#	app/components/chat/Messages.client.tsx
#	app/lib/persistence/db.ts
#	app/lib/persistence/useChatHistory.ts
  • Loading branch information
wonderwhy-er committed Nov 22, 2024
2 parents fb34a4c + 7fc8e40 commit f6a7c4f
Show file tree
Hide file tree
Showing 33 changed files with 645 additions and 357 deletions.
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,12 @@ LMSTUDIO_API_BASE_URL=
XAI_API_KEY=

# Include this environment variable if you want more logging for debugging locally
VITE_LOG_LEVEL=debug
VITE_LOG_LEVEL=debug

# Example Context Values for qwen2.5-coder:32b
#
# DEFAULT_NUM_CTX=32768 # Consumes 36GB of VRAM
# DEFAULT_NUM_CTX=24576 # Consumes 32GB of VRAM
# DEFAULT_NUM_CTX=12288 # Consumes 26GB of VRAM
# DEFAULT_NUM_CTX=6144 # Consumes 24GB of VRAM
DEFAULT_NUM_CTX=
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Contributing to Bolt.new Fork
## DEFAULT_NUM_CTX

The `DEFAULT_NUM_CTX` environment variable can be used to limit the maximum number of context values used by the qwen2.5-coder model. For example, to limit the context to 24576 values (which uses 32GB of VRAM), set `DEFAULT_NUM_CTX=24576` in your `.env.local` file.

First off, thank you for considering contributing to Bolt.new! This fork aims to expand the capabilities of the original project by integrating multiple LLM providers and enhancing functionality. Every contribution helps make Bolt.new a better tool for developers worldwide.

Expand Down Expand Up @@ -81,6 +84,19 @@ ANTHROPIC_API_KEY=XXX
```bash
VITE_LOG_LEVEL=debug
```

- Optionally set context size:
```bash
DEFAULT_NUM_CTX=32768
```

Some Example Context Values for the qwen2.5-coder:32b models are.

* DEFAULT_NUM_CTX=32768 - Consumes 36GB of VRAM
* DEFAULT_NUM_CTX=24576 - Consumes 32GB of VRAM
* DEFAULT_NUM_CTX=12288 - Consumes 26GB of VRAM
* DEFAULT_NUM_CTX=6144 - Consumes 24GB of VRAM

**Important**: Never commit your `.env.local` file to version control. It's already included in .gitignore.

### 🚀 Running the Development Server
Expand Down
8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ARG OPEN_ROUTER_API_KEY
ARG GOOGLE_GENERATIVE_AI_API_KEY
ARG OLLAMA_API_BASE_URL
ARG VITE_LOG_LEVEL=debug
ARG DEFAULT_NUM_CTX

ENV WRANGLER_SEND_METRICS=false \
GROQ_API_KEY=${GROQ_API_KEY} \
Expand All @@ -35,7 +36,8 @@ ENV WRANGLER_SEND_METRICS=false \
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY} \
OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL} \
VITE_LOG_LEVEL=${VITE_LOG_LEVEL}
VITE_LOG_LEVEL=${VITE_LOG_LEVEL} \
DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX}

# Pre-configure wrangler to disable metrics
RUN mkdir -p /root/.config/.wrangler && \
Expand All @@ -57,6 +59,7 @@ ARG OPEN_ROUTER_API_KEY
ARG GOOGLE_GENERATIVE_AI_API_KEY
ARG OLLAMA_API_BASE_URL
ARG VITE_LOG_LEVEL=debug
ARG DEFAULT_NUM_CTX

ENV GROQ_API_KEY=${GROQ_API_KEY} \
HuggingFace_API_KEY=${HuggingFace_API_KEY} \
Expand All @@ -65,7 +68,8 @@ ENV GROQ_API_KEY=${GROQ_API_KEY} \
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY} \
OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL} \
VITE_LOG_LEVEL=${VITE_LOG_LEVEL}
VITE_LOG_LEVEL=${VITE_LOG_LEVEL} \
DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX}

RUN mkdir -p ${WORKDIR}/run
CMD pnpm run dev --host
48 changes: 27 additions & 21 deletions app/components/chat/APIKeyManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ interface APIKeyManagerProps {
labelForGetApiKey?: string;
}

export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
provider,
apiKey,
setApiKey,
}) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey, setApiKey }) => {
const [isEditing, setIsEditing] = useState(false);
const [tempKey, setTempKey] = useState(apiKey);

Expand All @@ -24,36 +21,45 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
};

return (
<div className="flex items-center gap-2 mt-2 mb-2">
<span className="text-sm text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
<div className="flex items-start sm:items-center mt-2 mb-2 flex-col sm:flex-row">
<div>
<span className="text-sm text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
{!isEditing && (
<div className="flex items-center mb-4">
<span className="flex-1 text-xs text-bolt-elements-textPrimary mr-2">
{apiKey ? '••••••••' : 'Not set (will still work if set in .env file)'}
</span>
<IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
<div className="i-ph:pencil-simple" />
</IconButton>
</div>
)}
</div>

{isEditing ? (
<>
<div className="flex items-center gap-3 mt-2">
<input
type="password"
value={tempKey}
placeholder="Your API Key"
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"
className="flex-1 px-2 py-1 text-xs lg: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>
</>
</div>
) : (
<>
<span className="flex-1 text-sm text-bolt-elements-textPrimary">
{apiKey ? '••••••••' : 'Not set (will still work if set in .env file)'}
</span>
<IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
<div className="i-ph:pencil-simple" />
</IconButton>

{provider?.getApiKeyLink && <IconButton onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
<span className="mr-2">{provider?.labelForGetApiKey || 'Get API Key'}</span>
<div className={provider?.icon || "i-ph:key"} />
</IconButton>}
{provider?.getApiKeyLink && (
<IconButton className="ml-auto" onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
<span className="mr-2 text-xs lg:text-sm">{provider?.labelForGetApiKey || 'Get API Key'}</span>
<div className={provider?.icon || 'i-ph:key'} />
</IconButton>
)}
</>
)}
</div>
Expand Down
61 changes: 36 additions & 25 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @ts-nocheck
// Preventing TS checks with files presented in the video for a better presentation.
/*
* @ts-nocheck
* Preventing TS checks with files presented in the video for a better presentation.
*/
import type { Message } from 'ai';
import React, { type RefCallback, useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { Menu } from '~/components/sidebar/Menu.client';
import { IconButton } from '~/components/ui/IconButton';
import { Workbench } from '~/components/workbench/Workbench.client';
import { classNames } from '~/utils/classNames';
import { MODEL_LIST, DEFAULT_PROVIDER, PROVIDER_LIST, initializeModelList } from '~/utils/constants';
import { MODEL_LIST, PROVIDER_LIST, initializeModelList } from '~/utils/constants';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import { APIKeyManager } from './APIKeyManager';
Expand All @@ -28,21 +30,25 @@ const EXAMPLE_PROMPTS = [
{ text: 'How do I center a div?' }
];

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const providerList = PROVIDER_LIST;

const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList }) => {
// @ts-ignore TODO: Introduce proper types
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList, apiKeys }) => {
return (
<div className="mb-2 flex gap-2">
<div className="mb-2 flex gap-2 flex-col sm:flex-row">
<select
value={provider?.name}
onChange={(e) => {
setProvider(providerList.find((p) => p.name === e.target.value));
setProvider(providerList.find((p: ProviderInfo) => p.name === e.target.value));

const firstModel = [...modelList].find((m) => m.provider == e.target.value);
setModel(firstModel ? firstModel.name : '');
}}
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) => (
{providerList.map((provider: ProviderInfo) => (
<option key={provider.name} value={provider.name}>
{provider.name}
</option>
Expand All @@ -52,8 +58,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
key={provider?.name}
value={model}
onChange={(e) => setModel(e.target.value)}
style={{ maxWidth: '70%' }}
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"
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 lg:max-w-[70%] "
>
{[...modelList]
.filter((e) => e.provider == provider?.name && e.name)
Expand Down Expand Up @@ -128,14 +133,17 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
// Load API keys from cookies on component mount
try {
const storedApiKeys = Cookies.get('apiKeys');

if (storedApiKeys) {
const parsedKeys = JSON.parse(storedApiKeys);

if (typeof parsedKeys === 'object' && parsedKeys !== null) {
setApiKeys(parsedKeys);
}
}
} catch (error) {
console.error('Error loading API keys from cookies:', error);

// Clear invalid cookie data
Cookies.remove('apiKeys');
}
Expand All @@ -149,6 +157,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
try {
const updatedApiKeys = { ...apiKeys, [provider]: key };
setApiKeys(updatedApiKeys);

// Save updated API keys to cookies with 30 day expiry and secure settings
Cookies.set('apiKeys', JSON.stringify(updatedApiKeys), {
expires: 30, // 30 days
Expand All @@ -167,25 +176,25 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
ref={ref}
className={classNames(
styles.BaseChat,
'relative flex h-full w-full overflow-hidden bg-bolt-elements-background-depth-1'
'relative flex flex-col lg:flex-row 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')}>
<div ref={scrollRef} className="flex flex-col lg:flex-rowoverflow-y-auto w-full h-full">
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
{!chatStarted && (
<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">
<div id="intro" className="mt-[26vh] max-w-chat mx-auto text-centerpx-4 lg:px-0">
<h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
Where ideas begin
</h1>
<p className="text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
<p className="text-md lg: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>
)}
<div
className={classNames('pt-6 px-6', {
className={classNames('pt-6 px-2 sm:px-6', {
'h-full flex flex-col': chatStarted
})}
>
Expand All @@ -194,7 +203,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
return chatStarted ? (
<Messages
ref={messageRef}
className="flex flex-col w-full flex-1 max-w-chat px-4 pb-6 mx-auto z-1"
className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
messages={messages}
isStreaming={isStreaming}
/>
Expand All @@ -203,10 +212,11 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
</ClientOnly>
<div
className={classNames(
'bg-bolt-elements-background-depth-2 border-y border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt',
'bg-bolt-elements-background-depth-2 p-3 rounded-lg border border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt mb-6',
{
'sticky bottom-0': chatStarted
})}
'sticky bottom-2': chatStarted,
},
)}
>
<ModelSelector
key={provider?.name + ':' + modelList.length}
Expand All @@ -216,22 +226,23 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
provider={provider}
setProvider={setProvider}
providerList={PROVIDER_LIST}
/>
apiKeys={apiKeys}
/>
{provider && (
<APIKeyManager
provider={provider}
apiKey={apiKeys[provider.name] || ''}
setApiKey={(key) => updateApiKey(provider.name, key)}
/>
)}
setApiKey={(key) => updateApiKey(provider.name, key)}/>
)}

<div
className={classNames(
'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 focus:ring-2 focus:ring-bolt-elements-focus resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent transition-all`}
className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-0 focus:border-none focus:shadow-none 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
17 changes: 11 additions & 6 deletions app/components/chat/Chat.client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @ts-nocheck
// Preventing TS checks with files presented in the video for a better presentation.
/*
* @ts-nocheck
* Preventing TS checks with files presented in the video for a better presentation.
*/
import { useStore } from '@nanostores/react';
import type { Message } from 'ai';
import { useChat } from 'ai/react';
Expand Down Expand Up @@ -84,7 +86,7 @@ export const ChatImpl = memo(({ description, initialMessages, storeMessageHistor
});
const [provider, setProvider] = useState(() => {
const savedProvider = Cookies.get('selectedProvider');
return PROVIDER_LIST.find(p => p.name === savedProvider) || DEFAULT_PROVIDER;
return PROVIDER_LIST.find((p) => p.name === savedProvider) || DEFAULT_PROVIDER;
});

const { showChat } = useStore(chatStore);
Expand All @@ -96,11 +98,13 @@ export const ChatImpl = memo(({ description, initialMessages, storeMessageHistor
const { messages, isLoading, input, handleInputChange, setInput, stop, append } = useChat({
api: '/api/chat',
body: {
apiKeys
apiKeys,
},
onError: (error) => {
logger.error('Request failed\n\n', error);
toast.error('There was an error processing your request: ' + (error.message ? error.message : "No details were returned"));
toast.error(
'There was an error processing your request: ' + (error.message ? error.message : 'No details were returned'),
);
},
onFinish: () => {
logger.debug('Finished streaming');
Expand Down Expand Up @@ -221,6 +225,7 @@ export const ChatImpl = memo(({ description, initialMessages, storeMessageHistor

useEffect(() => {
const storedApiKeys = Cookies.get('apiKeys');

if (storedApiKeys) {
setApiKeys(JSON.parse(storedApiKeys));
}
Expand Down Expand Up @@ -277,7 +282,7 @@ export const ChatImpl = memo(({ description, initialMessages, storeMessageHistor
},
model,
provider,
apiKeys
apiKeys,
);
}}
/>
Expand Down
Loading

0 comments on commit f6a7c4f

Please sign in to comment.