merge with upstream/main
This commit is contained in:
@@ -1,47 +1,45 @@
|
||||
// @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 } from 'react';
|
||||
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 { useState } from 'react';
|
||||
import { APIKeyManager } from './APIKeyManager';
|
||||
import Cookies from 'js-cookie';
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import styles from './BaseChat.module.scss';
|
||||
import type { ProviderInfo } from '~/utils/types';
|
||||
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
|
||||
import { ImportButtons } from '~/components/chat/chatExportAndImport/ImportButtons';
|
||||
import { ExamplePrompts } from '~/components/chat/ExamplePrompts';
|
||||
|
||||
import FilePreview from './FilePreview';
|
||||
|
||||
const EXAMPLE_PROMPTS = [
|
||||
{ text: 'Build a todo app in React using Tailwind' },
|
||||
{ text: 'Build a simple blog using Astro' },
|
||||
{ text: 'Create a cookie consent form using Material UI' },
|
||||
{ text: 'Make a space invaders game' },
|
||||
{ text: 'How do I center a div?' },
|
||||
];
|
||||
|
||||
const providerList = PROVIDER_LIST;
|
||||
|
||||
// @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 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>
|
||||
@@ -75,6 +73,7 @@ interface BaseChatProps {
|
||||
chatStarted?: boolean;
|
||||
isStreaming?: boolean;
|
||||
messages?: Message[];
|
||||
description?: string;
|
||||
enhancingPrompt?: boolean;
|
||||
promptEnhanced?: boolean;
|
||||
input?: string;
|
||||
@@ -86,6 +85,8 @@ interface BaseChatProps {
|
||||
sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
|
||||
handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
enhancePrompt?: () => void;
|
||||
importChat?: (description: string, messages: Message[]) => Promise<void>;
|
||||
exportChat?: () => void;
|
||||
uploadedFiles?: File[];
|
||||
setUploadedFiles?: (files: File[]) => void;
|
||||
imageDataList?: string[];
|
||||
@@ -111,12 +112,13 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
enhancePrompt,
|
||||
sendMessage,
|
||||
handleStop,
|
||||
uploadedFiles,
|
||||
importChat,
|
||||
exportChat,
|
||||
uploadedFiles = [],
|
||||
setUploadedFiles,
|
||||
imageDataList,
|
||||
imageDataList = [],
|
||||
setImageDataList,
|
||||
messages,
|
||||
children, // Add this
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -128,14 +130,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');
|
||||
}
|
||||
@@ -149,6 +154,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
|
||||
@@ -161,11 +167,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = () => {
|
||||
setUploadedFiles([]);
|
||||
setImageDataList([]);
|
||||
};
|
||||
|
||||
const handleFileUpload = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
@@ -173,8 +174,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
const base64Image = e.target?.result as string;
|
||||
setUploadedFiles?.([...uploadedFiles, file]);
|
||||
@@ -187,7 +190,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
input.click();
|
||||
};
|
||||
|
||||
return (
|
||||
const baseChat = (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
@@ -300,6 +303,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
handleStop?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.length > 0 || uploadedFiles.length > 0) {
|
||||
sendMessage?.(event);
|
||||
}
|
||||
@@ -309,22 +313,19 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
</ClientOnly>
|
||||
<div className="flex justify-between items-center text-sm p-4 pt-2">
|
||||
<div className="flex gap-1 items-center">
|
||||
<IconButton
|
||||
title="Upload file"
|
||||
className="transition-all"
|
||||
onClick={() => handleFileUpload()}
|
||||
>
|
||||
<IconButton title="Upload file" className="transition-all" onClick={() => handleFileUpload()}>
|
||||
<div className="i-ph:paperclip text-xl"></div>
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
title="Enhance prompt"
|
||||
disabled={input.length === 0 || enhancingPrompt}
|
||||
className={classNames('transition-all', {
|
||||
'opacity-100!': enhancingPrompt,
|
||||
'text-bolt-elements-item-contentAccent! pr-1.5 enabled:hover:bg-bolt-elements-item-backgroundAccent!':
|
||||
promptEnhanced,
|
||||
})}
|
||||
className={classNames(
|
||||
'transition-all',
|
||||
enhancingPrompt ? 'opacity-100' : '',
|
||||
promptEnhanced ? 'text-bolt-elements-item-contentAccent' : '',
|
||||
promptEnhanced ? 'pr-1.5' : '',
|
||||
promptEnhanced ? 'enabled:hover:bg-bolt-elements-item-backgroundAccent' : '',
|
||||
)}
|
||||
onClick={() => enhancePrompt?.()}
|
||||
>
|
||||
{enhancingPrompt ? (
|
||||
@@ -339,6 +340,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
</>
|
||||
)}
|
||||
</IconButton>
|
||||
{chatStarted && <ClientOnly>{() => <ExportChatButton exportChat={exportChat} />}</ClientOnly>}
|
||||
</div>
|
||||
{input.length > 3 ? (
|
||||
<div className="text-xs text-bolt-elements-textTertiary">
|
||||
@@ -351,30 +353,14 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!chatStarted && (
|
||||
<div id="examples" className="relative w-full max-w-xl mx-auto mt-8 flex justify-center">
|
||||
<div className="flex flex-col space-y-2 [mask-image:linear-gradient(to_bottom,black_0%,transparent_180%)] hover:[mask-image:none]">
|
||||
{EXAMPLE_PROMPTS.map((examplePrompt, index) => {
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={(event) => {
|
||||
sendMessage?.(event, examplePrompt.text);
|
||||
}}
|
||||
className="group flex items-center w-full gap-2 justify-center bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary transition-theme"
|
||||
>
|
||||
{examplePrompt.text}
|
||||
<div className="i-ph:arrow-bend-down-left" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!chatStarted && ImportButtons(importChat)}
|
||||
{!chatStarted && ExamplePrompts(sendMessage)}
|
||||
</div>
|
||||
<ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <Tooltip.Provider delayDuration={200}>{baseChat}</Tooltip.Provider>;
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user