changing based on PR review
Browse files- app/components/chat/BaseChat.tsx +35 -35
- app/components/chat/FilePreview.tsx +6 -7
- app/components/chat/UserMessage.tsx +0 -3
- app/lib/.server/llm/stream-text.ts +2 -12
- app/routes/api.chat.ts +3 -10
- package.json +0 -1
- pnpm-lock.yaml +0 -12
app/components/chat/BaseChat.tsx
CHANGED
@@ -35,7 +35,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
|
|
35 |
<select
|
36 |
value={provider?.name}
|
37 |
onChange={(e) => {
|
38 |
-
setProvider(providerList.find(p => p.name === e.target.value));
|
39 |
const firstModel = [...modelList].find((m) => m.provider == e.target.value);
|
40 |
setModel(firstModel ? firstModel.name : '');
|
41 |
}}
|
@@ -51,7 +51,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
|
|
51 |
key={provider?.name}
|
52 |
value={model}
|
53 |
onChange={(e) => setModel(e.target.value)}
|
54 |
-
style={{ maxWidth:
|
55 |
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"
|
56 |
>
|
57 |
{[...modelList]
|
@@ -93,32 +93,34 @@ interface BaseChatProps {
|
|
93 |
setImageDataList?: (dataList: string[]) => void;
|
94 |
}
|
95 |
export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
96 |
-
(
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
122 |
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
|
123 |
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
|
124 |
const [modelList, setModelList] = useState(MODEL_LIST);
|
@@ -139,7 +141,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
139 |
Cookies.remove('apiKeys');
|
140 |
}
|
141 |
|
142 |
-
initializeModelList().then(modelList => {
|
143 |
setModelList(modelList);
|
144 |
});
|
145 |
}, []);
|
@@ -239,12 +241,13 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
239 |
setProvider={setProvider}
|
240 |
providerList={PROVIDER_LIST}
|
241 |
/>
|
242 |
-
{provider &&
|
243 |
<APIKeyManager
|
244 |
provider={provider}
|
245 |
apiKey={apiKeys[provider.name] || ''}
|
246 |
setApiKey={(key) => updateApiKey(provider.name, key)}
|
247 |
-
/>
|
|
|
248 |
|
249 |
<FilePreview
|
250 |
files={uploadedFiles}
|
@@ -309,7 +312,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
309 |
className="transition-all"
|
310 |
onClick={() => handleFileUpload()}
|
311 |
>
|
312 |
-
<div className="i-ph:
|
313 |
</IconButton>
|
314 |
|
315 |
<IconButton
|
@@ -374,6 +377,3 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
374 |
);
|
375 |
},
|
376 |
);
|
377 |
-
|
378 |
-
|
379 |
-
|
|
|
35 |
<select
|
36 |
value={provider?.name}
|
37 |
onChange={(e) => {
|
38 |
+
setProvider(providerList.find((p) => p.name === e.target.value));
|
39 |
const firstModel = [...modelList].find((m) => m.provider == e.target.value);
|
40 |
setModel(firstModel ? firstModel.name : '');
|
41 |
}}
|
|
|
51 |
key={provider?.name}
|
52 |
value={model}
|
53 |
onChange={(e) => setModel(e.target.value)}
|
54 |
+
style={{ maxWidth: '70%' }}
|
55 |
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"
|
56 |
>
|
57 |
{[...modelList]
|
|
|
93 |
setImageDataList?: (dataList: string[]) => void;
|
94 |
}
|
95 |
export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
96 |
+
(
|
97 |
+
{
|
98 |
+
textareaRef,
|
99 |
+
messageRef,
|
100 |
+
scrollRef,
|
101 |
+
showChat = true,
|
102 |
+
chatStarted = false,
|
103 |
+
isStreaming = false,
|
104 |
+
model,
|
105 |
+
setModel,
|
106 |
+
provider,
|
107 |
+
setProvider,
|
108 |
+
input = '',
|
109 |
+
enhancingPrompt,
|
110 |
+
handleInputChange,
|
111 |
+
promptEnhanced,
|
112 |
+
enhancePrompt,
|
113 |
+
sendMessage,
|
114 |
+
handleStop,
|
115 |
+
uploadedFiles,
|
116 |
+
setUploadedFiles,
|
117 |
+
imageDataList,
|
118 |
+
setImageDataList,
|
119 |
+
messages,
|
120 |
+
children, // Add this
|
121 |
+
},
|
122 |
+
ref,
|
123 |
+
) => {
|
124 |
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
|
125 |
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
|
126 |
const [modelList, setModelList] = useState(MODEL_LIST);
|
|
|
141 |
Cookies.remove('apiKeys');
|
142 |
}
|
143 |
|
144 |
+
initializeModelList().then((modelList) => {
|
145 |
setModelList(modelList);
|
146 |
});
|
147 |
}, []);
|
|
|
241 |
setProvider={setProvider}
|
242 |
providerList={PROVIDER_LIST}
|
243 |
/>
|
244 |
+
{provider && (
|
245 |
<APIKeyManager
|
246 |
provider={provider}
|
247 |
apiKey={apiKeys[provider.name] || ''}
|
248 |
setApiKey={(key) => updateApiKey(provider.name, key)}
|
249 |
+
/>
|
250 |
+
)}
|
251 |
|
252 |
<FilePreview
|
253 |
files={uploadedFiles}
|
|
|
312 |
className="transition-all"
|
313 |
onClick={() => handleFileUpload()}
|
314 |
>
|
315 |
+
<div className="i-ph:paperclip text-xl"></div>
|
316 |
</IconButton>
|
317 |
|
318 |
<IconButton
|
|
|
377 |
);
|
378 |
},
|
379 |
);
|
|
|
|
|
|
app/components/chat/FilePreview.tsx
CHANGED
@@ -1,23 +1,22 @@
|
|
1 |
-
//
|
2 |
import React from 'react';
|
3 |
-
import { X } from 'lucide-react';
|
4 |
|
|
|
5 |
interface FilePreviewProps {
|
6 |
files: File[];
|
7 |
-
imageDataList: string[];
|
8 |
onRemove: (index: number) => void;
|
9 |
}
|
10 |
|
11 |
const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, onRemove }) => {
|
12 |
if (!files || files.length === 0) {
|
13 |
-
return null;
|
14 |
}
|
15 |
|
16 |
return (
|
17 |
-
<div className="flex flex-row overflow-x-auto">
|
18 |
{files.map((file, index) => (
|
19 |
<div key={file.name + file.size} className="mr-2 relative">
|
20 |
-
{/* Display image preview or file icon */}
|
21 |
{imageDataList[index] && (
|
22 |
<div className="relative">
|
23 |
<img src={imageDataList[index]} alt={file.name} className="max-h-20" />
|
@@ -26,7 +25,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, onRemov
|
|
26 |
className="absolute -top-2 -right-2 z-10 bg-white rounded-full p-1 shadow-md hover:bg-gray-100"
|
27 |
>
|
28 |
<div className="bg-black rounded-full p-1">
|
29 |
-
<
|
30 |
</div>
|
31 |
</button>
|
32 |
</div>
|
|
|
1 |
+
// Remove the lucide-react import
|
2 |
import React from 'react';
|
|
|
3 |
|
4 |
+
// Rest of the interface remains the same
|
5 |
interface FilePreviewProps {
|
6 |
files: File[];
|
7 |
+
imageDataList: string[];
|
8 |
onRemove: (index: number) => void;
|
9 |
}
|
10 |
|
11 |
const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, onRemove }) => {
|
12 |
if (!files || files.length === 0) {
|
13 |
+
return null;
|
14 |
}
|
15 |
|
16 |
return (
|
17 |
+
<div className="flex flex-row overflow-x-auto">
|
18 |
{files.map((file, index) => (
|
19 |
<div key={file.name + file.size} className="mr-2 relative">
|
|
|
20 |
{imageDataList[index] && (
|
21 |
<div className="relative">
|
22 |
<img src={imageDataList[index]} alt={file.name} className="max-h-20" />
|
|
|
25 |
className="absolute -top-2 -right-2 z-10 bg-white rounded-full p-1 shadow-md hover:bg-gray-100"
|
26 |
>
|
27 |
<div className="bg-black rounded-full p-1">
|
28 |
+
<div className="i-ph:x w-3 h-3 text-gray-400" />
|
29 |
</div>
|
30 |
</button>
|
31 |
</div>
|
app/components/chat/UserMessage.tsx
CHANGED
@@ -21,9 +21,6 @@ export function UserMessage({ content }: UserMessageProps) {
|
|
21 |
);
|
22 |
}
|
23 |
|
24 |
-
// function sanitizeUserMessage(content: string) {
|
25 |
-
// return content.replace(modificationsRegex, '').replace(MODEL_REGEX, 'Using: $1').replace(PROVIDER_REGEX, ' ($1)\n\n').trim();
|
26 |
-
// }
|
27 |
function sanitizeUserMessage(content: string | Array<{type: string, text?: string, image_url?: {url: string}}>) {
|
28 |
if (Array.isArray(content)) {
|
29 |
return content.map(item => {
|
|
|
21 |
);
|
22 |
}
|
23 |
|
|
|
|
|
|
|
24 |
function sanitizeUserMessage(content: string | Array<{type: string, text?: string, image_url?: {url: string}}>) {
|
25 |
if (Array.isArray(content)) {
|
26 |
return content.map(item => {
|
app/lib/.server/llm/stream-text.ts
CHANGED
@@ -45,12 +45,12 @@ function extractPropertiesFromMessage(message: Message): { model: string; provid
|
|
45 |
if (item.type === 'text') {
|
46 |
return {
|
47 |
type: 'text',
|
48 |
-
text: item.text?.replace(
|
49 |
};
|
50 |
}
|
51 |
return item; // Preserve image_url and other types as is
|
52 |
})
|
53 |
-
: textContent.replace(
|
54 |
|
55 |
return { model, provider, content: cleanedContent };
|
56 |
}
|
@@ -80,16 +80,6 @@ export function streamText(
|
|
80 |
return message; // No changes for non-user messages
|
81 |
});
|
82 |
|
83 |
-
// const modelConfig = getModel(currentProvider, currentModel, env, apiKeys);
|
84 |
-
// const coreMessages = convertToCoreMessages(processedMessages);
|
85 |
-
|
86 |
-
// console.log('Debug streamText:', JSON.stringify({
|
87 |
-
// model: modelConfig,
|
88 |
-
// messages: processedMessages,
|
89 |
-
// coreMessages: coreMessages,
|
90 |
-
// system: getSystemPrompt()
|
91 |
-
// }, null, 2));
|
92 |
-
|
93 |
return _streamText({
|
94 |
model: getModel(currentProvider, currentModel, env, apiKeys),
|
95 |
system: getSystemPrompt(),
|
|
|
45 |
if (item.type === 'text') {
|
46 |
return {
|
47 |
type: 'text',
|
48 |
+
text: item.text?.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '')
|
49 |
};
|
50 |
}
|
51 |
return item; // Preserve image_url and other types as is
|
52 |
})
|
53 |
+
: textContent.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
|
54 |
|
55 |
return { model, provider, content: cleanedContent };
|
56 |
}
|
|
|
80 |
return message; // No changes for non-user messages
|
81 |
});
|
82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
return _streamText({
|
84 |
model: getModel(currentProvider, currentModel, env, apiKeys),
|
85 |
system: getSystemPrompt(),
|
app/routes/api.chat.ts
CHANGED
@@ -30,15 +30,15 @@ function parseCookies(cookieHeader) {
|
|
30 |
}
|
31 |
|
32 |
async function chatAction({ context, request }: ActionFunctionArgs) {
|
33 |
-
|
34 |
-
// console.log('Request received:', request.url);
|
35 |
-
|
36 |
const { messages, imageData } = await request.json<{
|
37 |
messages: Messages,
|
38 |
imageData?: string[]
|
39 |
}>();
|
40 |
|
41 |
const cookieHeader = request.headers.get("Cookie");
|
|
|
|
|
42 |
const apiKeys = JSON.parse(parseCookies(cookieHeader).apiKeys || "{}");
|
43 |
|
44 |
const stream = new SwitchableStream();
|
@@ -71,13 +71,6 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
|
71 |
|
72 |
const result = await streamText(messages, context.cloudflare.env, options, apiKeys);
|
73 |
|
74 |
-
// console.log('=== API CHAT LOGGING START ===');
|
75 |
-
// console.log('StreamText:', JSON.stringify({
|
76 |
-
// messages,
|
77 |
-
// result,
|
78 |
-
// }, null, 2));
|
79 |
-
// console.log('=== API CHAT LOGGING END ===');
|
80 |
-
|
81 |
stream.switchSource(result.toAIStream());
|
82 |
|
83 |
return new Response(stream.readable, {
|
|
|
30 |
}
|
31 |
|
32 |
async function chatAction({ context, request }: ActionFunctionArgs) {
|
33 |
+
|
|
|
|
|
34 |
const { messages, imageData } = await request.json<{
|
35 |
messages: Messages,
|
36 |
imageData?: string[]
|
37 |
}>();
|
38 |
|
39 |
const cookieHeader = request.headers.get("Cookie");
|
40 |
+
|
41 |
+
// Parse the cookie's value (returns an object or null if no cookie exists)
|
42 |
const apiKeys = JSON.parse(parseCookies(cookieHeader).apiKeys || "{}");
|
43 |
|
44 |
const stream = new SwitchableStream();
|
|
|
71 |
|
72 |
const result = await streamText(messages, context.cloudflare.env, options, apiKeys);
|
73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
stream.switchSource(result.toAIStream());
|
75 |
|
76 |
return new Response(stream.readable, {
|
package.json
CHANGED
@@ -74,7 +74,6 @@
|
|
74 |
"jose": "^5.6.3",
|
75 |
"js-cookie": "^3.0.5",
|
76 |
"jszip": "^3.10.1",
|
77 |
-
"lucide-react": "^0.460.0",
|
78 |
"nanostores": "^0.10.3",
|
79 |
"ollama-ai-provider": "^0.15.2",
|
80 |
"react": "^18.2.0",
|
|
|
74 |
"jose": "^5.6.3",
|
75 |
"js-cookie": "^3.0.5",
|
76 |
"jszip": "^3.10.1",
|
|
|
77 |
"nanostores": "^0.10.3",
|
78 |
"ollama-ai-provider": "^0.15.2",
|
79 |
"react": "^18.2.0",
|
pnpm-lock.yaml
CHANGED
@@ -155,9 +155,6 @@ importers:
|
|
155 |
jszip:
|
156 |
specifier: ^3.10.1
|
157 |
version: 3.10.1
|
158 |
-
lucide-react:
|
159 |
-
specifier: ^0.460.0
|
160 |
-
version: 0.460.0([email protected])
|
161 |
nanostores:
|
162 |
specifier: ^0.10.3
|
163 |
version: 0.10.3
|
@@ -3674,11 +3671,6 @@ packages:
|
|
3674 |
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
3675 |
engines: {node: '>=12'}
|
3676 |
|
3677 | |
3678 |
-
resolution: {integrity: sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==}
|
3679 |
-
peerDependencies:
|
3680 |
-
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
|
3681 |
-
|
3682 | |
3683 |
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
3684 |
|
@@ -9492,10 +9484,6 @@ snapshots:
|
|
9492 |
|
9493 | |
9494 |
|
9495 | |
9496 |
-
dependencies:
|
9497 |
-
react: 18.3.1
|
9498 |
-
|
9499 | |
9500 |
dependencies:
|
9501 |
sourcemap-codec: 1.4.8
|
|
|
155 |
jszip:
|
156 |
specifier: ^3.10.1
|
157 |
version: 3.10.1
|
|
|
|
|
|
|
158 |
nanostores:
|
159 |
specifier: ^0.10.3
|
160 |
version: 0.10.3
|
|
|
3671 |
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
3672 |
engines: {node: '>=12'}
|
3673 |
|
|
|
|
|
|
|
|
|
|
|
3674 | |
3675 |
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
3676 |
|
|
|
9484 |
|
9485 | |
9486 |
|
|
|
|
|
|
|
|
|
9487 | |
9488 |
dependencies:
|
9489 |
sourcemap-codec: 1.4.8
|