codacus commited on
Commit
fad4197
·
unverified ·
1 Parent(s): 3a298f1

fix: api-key manager cleanup and log error on llm call (#1077)

Browse files

* fix: api-key manager cleanup and log error on llm call

* log improved

app/components/chat/APIKeyManager.tsx CHANGED
@@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback } from 'react';
2
  import { IconButton } from '~/components/ui/IconButton';
3
  import type { ProviderInfo } from '~/types/model';
4
  import Cookies from 'js-cookie';
5
- import { providerBaseUrlEnvKeys } from '~/utils/constants';
6
 
7
  interface APIKeyManagerProps {
8
  provider: ProviderInfo;
@@ -93,17 +92,15 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey,
93
  <span className="text-sm font-medium text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
94
  {!isEditing && (
95
  <div className="flex items-center gap-2">
96
- {isEnvKeySet ? (
97
  <>
98
  <div className="i-ph:check-circle-fill text-green-500 w-4 h-4" />
99
- <span className="text-xs text-green-500">
100
- Set via {providerBaseUrlEnvKeys[provider.name].apiTokenKey} environment variable
101
- </span>
102
  </>
103
- ) : apiKey ? (
104
  <>
105
  <div className="i-ph:check-circle-fill text-green-500 w-4 h-4" />
106
- <span className="text-xs text-green-500">Set via UI</span>
107
  </>
108
  ) : (
109
  <>
@@ -117,7 +114,7 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey,
117
  </div>
118
 
119
  <div className="flex items-center gap-2 shrink-0">
120
- {isEditing && !isEnvKeySet ? (
121
  <div className="flex items-center gap-2">
122
  <input
123
  type="password"
@@ -145,7 +142,7 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey,
145
  </div>
146
  ) : (
147
  <>
148
- {!isEnvKeySet && (
149
  <IconButton
150
  onClick={() => setIsEditing(true)}
151
  title="Edit API Key"
@@ -153,8 +150,8 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey,
153
  >
154
  <div className="i-ph:pencil-simple w-4 h-4" />
155
  </IconButton>
156
- )}
157
- {provider?.getApiKeyLink && !isEnvKeySet && (
158
  <IconButton
159
  onClick={() => window.open(provider?.getApiKeyLink)}
160
  title="Get API Key"
 
2
  import { IconButton } from '~/components/ui/IconButton';
3
  import type { ProviderInfo } from '~/types/model';
4
  import Cookies from 'js-cookie';
 
5
 
6
  interface APIKeyManagerProps {
7
  provider: ProviderInfo;
 
92
  <span className="text-sm font-medium text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
93
  {!isEditing && (
94
  <div className="flex items-center gap-2">
95
+ {apiKey ? (
96
  <>
97
  <div className="i-ph:check-circle-fill text-green-500 w-4 h-4" />
98
+ <span className="text-xs text-green-500">Set via UI</span>
 
 
99
  </>
100
+ ) : isEnvKeySet ? (
101
  <>
102
  <div className="i-ph:check-circle-fill text-green-500 w-4 h-4" />
103
+ <span className="text-xs text-green-500">Set via environment variable</span>
104
  </>
105
  ) : (
106
  <>
 
114
  </div>
115
 
116
  <div className="flex items-center gap-2 shrink-0">
117
+ {isEditing ? (
118
  <div className="flex items-center gap-2">
119
  <input
120
  type="password"
 
142
  </div>
143
  ) : (
144
  <>
145
+ {
146
  <IconButton
147
  onClick={() => setIsEditing(true)}
148
  title="Edit API Key"
 
150
  >
151
  <div className="i-ph:pencil-simple w-4 h-4" />
152
  </IconButton>
153
+ }
154
+ {provider?.getApiKeyLink && !apiKey && (
155
  <IconButton
156
  onClick={() => window.open(provider?.getApiKeyLink)}
157
  title="Get API Key"
app/components/chat/Chat.client.tsx CHANGED
@@ -137,35 +137,36 @@ export const ChatImpl = memo(
137
 
138
  const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
139
 
140
- const { messages, isLoading, input, handleInputChange, setInput, stop, append, setMessages, reload } = useChat({
141
- api: '/api/chat',
142
- body: {
143
- apiKeys,
144
- files,
145
- promptId,
146
- contextOptimization: contextOptimizationEnabled,
147
- },
148
- sendExtraMessageFields: true,
149
- onError: (error) => {
150
- logger.error('Request failed\n\n', error);
151
- toast.error(
152
- 'There was an error processing your request: ' + (error.message ? error.message : 'No details were returned'),
153
- );
154
- },
155
- onFinish: (message, response) => {
156
- const usage = response.usage;
157
-
158
- if (usage) {
159
- console.log('Token usage:', usage);
160
-
161
- // You can now use the usage data as needed
162
- }
163
 
164
- logger.debug('Finished streaming');
165
- },
166
- initialMessages,
167
- initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
168
- });
 
 
 
 
 
 
169
  useEffect(() => {
170
  const prompt = searchParams.get('prompt');
171
 
@@ -263,6 +264,10 @@ export const ChatImpl = memo(
263
  */
264
  await workbenchStore.saveAllFiles();
265
 
 
 
 
 
266
  const fileModifications = workbenchStore.getFileModifcations();
267
 
268
  chatStore.setKey('aborted', false);
 
137
 
138
  const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
139
 
140
+ const { messages, isLoading, input, handleInputChange, setInput, stop, append, setMessages, reload, error } =
141
+ useChat({
142
+ api: '/api/chat',
143
+ body: {
144
+ apiKeys,
145
+ files,
146
+ promptId,
147
+ contextOptimization: contextOptimizationEnabled,
148
+ },
149
+ sendExtraMessageFields: true,
150
+ onError: (e) => {
151
+ logger.error('Request failed\n\n', e, error);
152
+ toast.error(
153
+ 'There was an error processing your request: ' + (e.message ? e.message : 'No details were returned'),
154
+ );
155
+ },
156
+ onFinish: (message, response) => {
157
+ const usage = response.usage;
 
 
 
 
 
158
 
159
+ if (usage) {
160
+ console.log('Token usage:', usage);
161
+
162
+ // You can now use the usage data as needed
163
+ }
164
+
165
+ logger.debug('Finished streaming');
166
+ },
167
+ initialMessages,
168
+ initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
169
+ });
170
  useEffect(() => {
171
  const prompt = searchParams.get('prompt');
172
 
 
264
  */
265
  await workbenchStore.saveAllFiles();
266
 
267
+ if (error != null) {
268
+ setMessages(messages.slice(0, -1));
269
+ }
270
+
271
  const fileModifications = workbenchStore.getFileModifcations();
272
 
273
  chatStore.setKey('aborted', false);
app/lib/.server/llm/stream-text.ts CHANGED
@@ -226,7 +226,7 @@ export async function streamText(props: {
226
 
227
  logger.info(`Sending llm call to ${provider.name} with model ${modelDetails.name}`);
228
 
229
- return _streamText({
230
  model: provider.getModelInstance({
231
  model: currentModel,
232
  serverEnv,
 
226
 
227
  logger.info(`Sending llm call to ${provider.name} with model ${modelDetails.name}`);
228
 
229
+ return await _streamText({
230
  model: provider.getModelInstance({
231
  model: currentModel,
232
  serverEnv,
app/routes/api.chat.ts CHANGED
@@ -122,6 +122,8 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
122
  return;
123
  },
124
  };
 
 
125
 
126
  const result = await streamText({
127
  messages,
@@ -134,13 +136,27 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
134
  contextOptimization,
135
  });
136
 
 
 
 
 
 
 
 
 
 
 
 
137
  stream.switchSource(result.toDataStream());
138
 
 
139
  return new Response(stream.readable, {
140
  status: 200,
141
  headers: {
142
- contentType: 'text/event-stream',
143
- connection: 'keep-alive',
 
 
144
  },
145
  });
146
  } catch (error: any) {
 
122
  return;
123
  },
124
  };
125
+ const totalMessageContent = messages.reduce((acc, message) => acc + message.content, '');
126
+ logger.debug(`Total message length: ${totalMessageContent.split(' ').length}, words`);
127
 
128
  const result = await streamText({
129
  messages,
 
136
  contextOptimization,
137
  });
138
 
139
+ (async () => {
140
+ for await (const part of result.fullStream) {
141
+ if (part.type === 'error') {
142
+ const error: any = part.error;
143
+ logger.error(`${error}`);
144
+
145
+ return;
146
+ }
147
+ }
148
+ })();
149
+
150
  stream.switchSource(result.toDataStream());
151
 
152
+ // return createrespo
153
  return new Response(stream.readable, {
154
  status: 200,
155
  headers: {
156
+ 'Content-Type': 'text/event-stream; charset=utf-8',
157
+ Connection: 'keep-alive',
158
+ 'Cache-Control': 'no-cache',
159
+ 'Text-Encoding': 'chunked',
160
  },
161
  });
162
  } catch (error: any) {
app/routes/api.check-env-key.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { LoaderFunction } from '@remix-run/node';
2
  import { providerBaseUrlEnvKeys } from '~/utils/constants';
3
 
4
  export const loader: LoaderFunction = async ({ context, request }) => {
 
1
+ import type { LoaderFunction } from '@remix-run/cloudflare';
2
  import { providerBaseUrlEnvKeys } from '~/utils/constants';
3
 
4
  export const loader: LoaderFunction = async ({ context, request }) => {
app/routes/api.enhancer.ts CHANGED
@@ -107,7 +107,10 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
107
  return new Response(result.textStream, {
108
  status: 200,
109
  headers: {
110
- 'Content-Type': 'text/plain; charset=utf-8',
 
 
 
111
  },
112
  });
113
  } catch (error: unknown) {
 
107
  return new Response(result.textStream, {
108
  status: 200,
109
  headers: {
110
+ 'Content-Type': 'text/event-stream',
111
+ Connection: 'keep-alive',
112
+ 'Cache-Control': 'no-cache',
113
+ 'Text-Encoding': 'chunked',
114
  },
115
  });
116
  } catch (error: unknown) {