Eduards commited on
Commit
756d3f2
·
1 Parent(s): a2cca14
app/components/chat/APIKeyManager.tsx CHANGED
@@ -6,9 +6,15 @@ interface APIKeyManagerProps {
6
  provider: ProviderInfo;
7
  apiKey: string;
8
  setApiKey: (key: string) => void;
 
 
9
  }
10
 
11
- export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey, setApiKey }) => {
 
 
 
 
12
  const [isEditing, setIsEditing] = useState(false);
13
  const [tempKey, setTempKey] = useState(apiKey);
14
 
@@ -43,7 +49,11 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey,
43
  <IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
44
  <div className="i-ph:pencil-simple" />
45
  </IconButton>
46
- {!!provider?.getApiKeyLink ? <a href={provider?.getApiKeyLink}>{provider?.labelForGetApiKey || "Get API Key"}</a> : "" }
 
 
 
 
47
  </>
48
  )}
49
  </div>
 
6
  provider: ProviderInfo;
7
  apiKey: string;
8
  setApiKey: (key: string) => void;
9
+ getApiKeyLink?: string;
10
+ labelForGetApiKey?: string;
11
  }
12
 
13
+ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
14
+ provider,
15
+ apiKey,
16
+ setApiKey,
17
+ }) => {
18
  const [isEditing, setIsEditing] = useState(false);
19
  const [tempKey, setTempKey] = useState(apiKey);
20
 
 
49
  <IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
50
  <div className="i-ph:pencil-simple" />
51
  </IconButton>
52
+
53
+ {provider?.getApiKeyLink && <IconButton onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
54
+ <span className="mr-2">{provider?.labelForGetApiKey || 'Get API Key'}</span>
55
+ <div className={provider?.icon || "i-ph:key"} />
56
+ </IconButton>}
57
  </>
58
  )}
59
  </div>
app/components/chat/BaseChat.tsx CHANGED
@@ -7,7 +7,7 @@ import { Menu } from '~/components/sidebar/Menu.client';
7
  import { IconButton } from '~/components/ui/IconButton';
8
  import { Workbench } from '~/components/workbench/Workbench.client';
9
  import { classNames } from '~/utils/classNames';
10
- import { MODEL_LIST, DEFAULT_PROVIDER, PROVIDER_LIST } from '~/utils/constants';
11
  import { Messages } from './Messages.client';
12
  import { SendButton } from './SendButton.client';
13
  import { useState } from 'react';
@@ -30,9 +30,9 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
30
  return (
31
  <div className="mb-2 flex gap-2">
32
  <select
33
- value={provider}
34
  onChange={(e) => {
35
- setProvider(e.target.value);
36
  const firstModel = [...modelList].find(m => m.provider == e.target.value);
37
  setModel(firstModel ? firstModel.name : '');
38
  }}
@@ -49,7 +49,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
49
  onChange={(e) => setModel(e.target.value)}
50
  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"
51
  >
52
- {[...modelList].filter(e => e.provider == provider && e.name).map((modelOption) => (
53
  <option key={modelOption.name} value={modelOption.name}>
54
  {modelOption.label}
55
  </option>
@@ -74,8 +74,8 @@ interface BaseChatProps {
74
  input?: string;
75
  model: string;
76
  setModel: (model: string) => void;
77
- provider: string;
78
- setProvider: (provider: string) => void;
79
  handleStop?: () => void;
80
  sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
81
  handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
@@ -106,6 +106,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
106
  },
107
  ref,
108
  ) => {
 
109
  const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
110
  const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
111
 
@@ -194,11 +195,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
194
  setProvider={setProvider}
195
  providerList={providerList}
196
  />
197
- <APIKeyManager
198
- provider={provider}
199
- apiKey={apiKeys[provider] || ''}
200
- setApiKey={(key) => updateApiKey(provider, key)}
201
- />
 
202
  <div
203
  className={classNames(
204
  'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
 
7
  import { IconButton } from '~/components/ui/IconButton';
8
  import { Workbench } from '~/components/workbench/Workbench.client';
9
  import { classNames } from '~/utils/classNames';
10
+ import { MODEL_LIST, DEFAULT_PROVIDER, PROVIDER_LIST, ProviderInfo } from '~/utils/constants';
11
  import { Messages } from './Messages.client';
12
  import { SendButton } from './SendButton.client';
13
  import { useState } from 'react';
 
30
  return (
31
  <div className="mb-2 flex gap-2">
32
  <select
33
+ value={provider?.name}
34
  onChange={(e) => {
35
+ setProvider(providerList.find(p => p.name === e.target.value));
36
  const firstModel = [...modelList].find(m => m.provider == e.target.value);
37
  setModel(firstModel ? firstModel.name : '');
38
  }}
 
49
  onChange={(e) => setModel(e.target.value)}
50
  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"
51
  >
52
+ {[...modelList].filter(e => e.provider == provider?.name && e.name).map((modelOption) => (
53
  <option key={modelOption.name} value={modelOption.name}>
54
  {modelOption.label}
55
  </option>
 
74
  input?: string;
75
  model: string;
76
  setModel: (model: string) => void;
77
+ provider: ProviderInfo;
78
+ setProvider: (provider: ProviderInfo) => void;
79
  handleStop?: () => void;
80
  sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
81
  handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
 
106
  },
107
  ref,
108
  ) => {
109
+ console.log(provider);
110
  const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
111
  const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
112
 
 
195
  setProvider={setProvider}
196
  providerList={providerList}
197
  />
198
+ {provider &&
199
+ <APIKeyManager
200
+ provider={provider}
201
+ apiKey={apiKeys[provider.name] || ''}
202
+ setApiKey={(key) => updateApiKey(provider.name, key)}
203
+ />}
204
  <div
205
  className={classNames(
206
  'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
app/components/chat/Chat.client.tsx CHANGED
@@ -189,7 +189,7 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
189
  * manually reset the input and we'd have to manually pass in file attachments. However, those
190
  * aren't relevant here.
191
  */
192
- append({ role: 'user', content: `[Model: ${model}]\n\n[Provider: ${provider}]\n\n${diff}\n\n${_input}` });
193
 
194
  /**
195
  * After sending a new message we reset all modifications since the model
@@ -197,7 +197,7 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
197
  */
198
  workbenchStore.resetAllFileModifications();
199
  } else {
200
- append({ role: 'user', content: `[Model: ${model}]\n\n[Provider: ${provider}]\n\n${_input}` });
201
  }
202
 
203
  setInput('');
 
189
  * manually reset the input and we'd have to manually pass in file attachments. However, those
190
  * aren't relevant here.
191
  */
192
+ append({ role: 'user', content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${diff}\n\n${_input}` });
193
 
194
  /**
195
  * After sending a new message we reset all modifications since the model
 
197
  */
198
  workbenchStore.resetAllFileModifications();
199
  } else {
200
+ append({ role: 'user', content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${_input}` });
201
  }
202
 
203
  setInput('');
app/utils/constants.ts CHANGED
@@ -6,7 +6,7 @@ export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications';
6
  export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
7
  export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
8
  export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
9
- export const DEFAULT_PROVIDER = 'Anthropic';
10
 
11
  export type ProviderInfo = {
12
  staticModels: ModelInfo[],
@@ -14,13 +14,29 @@ export type ProviderInfo = {
14
  getDynamicModels?: () => Promise<ModelInfo[]>,
15
  getApiKeyLink?: string,
16
  labelForGetApiKey?: string,
 
17
  };
18
 
19
  const PROVIDER_LIST: ProviderInfo[] = [
 
 
 
 
 
 
 
 
 
 
 
 
20
  {
21
  name: 'Ollama',
22
  staticModels: [],
23
- getDynamicModels: getOllamaModels
 
 
 
24
  }, {
25
  name: 'OpenAILike',
26
  staticModels: [],
@@ -62,17 +78,6 @@ const PROVIDER_LIST: ProviderInfo[] = [
62
  { name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq' }
63
  ],
64
  getApiKeyLink: 'https://console.groq.com/keys'
65
- }, {
66
- name: 'Anthropic',
67
- staticModels: [
68
- { name: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet (new)', provider: 'Anthropic' },
69
- { name: 'claude-3-5-sonnet-20240620', label: 'Claude 3.5 Sonnet (old)', provider: 'Anthropic' },
70
- { name: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku (new)', provider: 'Anthropic' },
71
- { name: 'claude-3-opus-latest', label: 'Claude 3 Opus', provider: 'Anthropic' },
72
- { name: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', provider: 'Anthropic' },
73
- { name: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', provider: 'Anthropic' }
74
- ],
75
- getApiKeyLink: "https://console.anthropic.com/settings/keys",
76
  }, {
77
  name: 'OpenAI',
78
  staticModels: [
@@ -114,10 +119,12 @@ const PROVIDER_LIST: ProviderInfo[] = [
114
  staticModels: [],
115
  getDynamicModels: getLMStudioModels,
116
  getApiKeyLink: 'https://lmstudio.ai/',
117
- labelForGetApiKey: 'Get LMStudio'
 
118
  }
119
  ];
120
 
 
121
 
122
  const staticModels: ModelInfo[] = PROVIDER_LIST.map(p => p.staticModels).flat();
123
 
 
6
  export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
7
  export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
8
  export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
9
+
10
 
11
  export type ProviderInfo = {
12
  staticModels: ModelInfo[],
 
14
  getDynamicModels?: () => Promise<ModelInfo[]>,
15
  getApiKeyLink?: string,
16
  labelForGetApiKey?: string,
17
+ icon?:string,
18
  };
19
 
20
  const PROVIDER_LIST: ProviderInfo[] = [
21
+ {
22
+ name: 'Anthropic',
23
+ staticModels: [
24
+ { name: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet (new)', provider: 'Anthropic' },
25
+ { name: 'claude-3-5-sonnet-20240620', label: 'Claude 3.5 Sonnet (old)', provider: 'Anthropic' },
26
+ { name: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku (new)', provider: 'Anthropic' },
27
+ { name: 'claude-3-opus-latest', label: 'Claude 3 Opus', provider: 'Anthropic' },
28
+ { name: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', provider: 'Anthropic' },
29
+ { name: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', provider: 'Anthropic' }
30
+ ],
31
+ getApiKeyLink: "https://console.anthropic.com/settings/keys",
32
+ },
33
  {
34
  name: 'Ollama',
35
  staticModels: [],
36
+ getDynamicModels: getOllamaModels,
37
+ getApiKeyLink: "https://ollama.com/download",
38
+ labelForGetApiKey: "Download Ollama",
39
+ icon: "i-ph:cloud-arrow-down",
40
  }, {
41
  name: 'OpenAILike',
42
  staticModels: [],
 
78
  { name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq' }
79
  ],
80
  getApiKeyLink: 'https://console.groq.com/keys'
 
 
 
 
 
 
 
 
 
 
 
81
  }, {
82
  name: 'OpenAI',
83
  staticModels: [
 
119
  staticModels: [],
120
  getDynamicModels: getLMStudioModels,
121
  getApiKeyLink: 'https://lmstudio.ai/',
122
+ labelForGetApiKey: 'Get LMStudio',
123
+ icon: "i-ph:cloud-arrow-down",
124
  }
125
  ];
126
 
127
+ export const DEFAULT_PROVIDER = PROVIDER_LIST[0];
128
 
129
  const staticModels: ModelInfo[] = PROVIDER_LIST.map(p => p.staticModels).flat();
130