mirror of
https://github.com/stackblitz/bolt.new
synced 2025-02-06 04:48:04 +00:00
commit
0b75051ba5
@ -74,8 +74,14 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
|
|||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
|
const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
|
||||||
const [model, setModel] = useState(DEFAULT_MODEL);
|
const [model, setModel] = useState(() => {
|
||||||
const [provider, setProvider] = useState(DEFAULT_PROVIDER);
|
const savedModel = Cookies.get('selectedModel');
|
||||||
|
return savedModel || DEFAULT_MODEL;
|
||||||
|
});
|
||||||
|
const [provider, setProvider] = useState(() => {
|
||||||
|
const savedProvider = Cookies.get('selectedProvider');
|
||||||
|
return savedProvider || DEFAULT_PROVIDER;
|
||||||
|
});
|
||||||
|
|
||||||
const { showChat } = useStore(chatStore);
|
const { showChat } = useStore(chatStore);
|
||||||
|
|
||||||
@ -216,6 +222,16 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleModelChange = (newModel: string) => {
|
||||||
|
setModel(newModel);
|
||||||
|
Cookies.set('selectedModel', newModel, { expires: 30 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProviderChange = (newProvider: string) => {
|
||||||
|
setProvider(newProvider);
|
||||||
|
Cookies.set('selectedProvider', newProvider, { expires: 30 });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseChat
|
<BaseChat
|
||||||
ref={animationScope}
|
ref={animationScope}
|
||||||
@ -228,9 +244,9 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
|
|||||||
promptEnhanced={promptEnhanced}
|
promptEnhanced={promptEnhanced}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
model={model}
|
model={model}
|
||||||
setModel={setModel}
|
setModel={handleModelChange}
|
||||||
provider={provider}
|
provider={provider}
|
||||||
setProvider={setProvider}
|
setProvider={handleProviderChange}
|
||||||
messageRef={messageRef}
|
messageRef={messageRef}
|
||||||
scrollRef={scrollRef}
|
scrollRef={scrollRef}
|
||||||
handleInputChange={handleInputChange}
|
handleInputChange={handleInputChange}
|
||||||
@ -246,10 +262,16 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
|
|||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
enhancePrompt={() => {
|
enhancePrompt={() => {
|
||||||
enhancePrompt(input, (input) => {
|
enhancePrompt(
|
||||||
setInput(input);
|
input,
|
||||||
scrollTextArea();
|
(input) => {
|
||||||
});
|
setInput(input);
|
||||||
|
scrollTextArea();
|
||||||
|
},
|
||||||
|
model,
|
||||||
|
provider,
|
||||||
|
apiKeys
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -12,41 +12,55 @@ export function usePromptEnhancer() {
|
|||||||
setPromptEnhanced(false);
|
setPromptEnhanced(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const enhancePrompt = async (input: string, setInput: (value: string) => void) => {
|
const enhancePrompt = async (
|
||||||
|
input: string,
|
||||||
|
setInput: (value: string) => void,
|
||||||
|
model: string,
|
||||||
|
provider: string,
|
||||||
|
apiKeys?: Record<string, string>
|
||||||
|
) => {
|
||||||
setEnhancingPrompt(true);
|
setEnhancingPrompt(true);
|
||||||
setPromptEnhanced(false);
|
setPromptEnhanced(false);
|
||||||
|
|
||||||
|
const requestBody: any = {
|
||||||
|
message: input,
|
||||||
|
model,
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apiKeys) {
|
||||||
|
requestBody.apiKeys = apiKeys;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/enhancer', {
|
const response = await fetch('/api/enhancer', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestBody),
|
||||||
message: input,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
const reader = response.body?.getReader();
|
||||||
|
|
||||||
const originalInput = input;
|
const originalInput = input;
|
||||||
|
|
||||||
if (reader) {
|
if (reader) {
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
let _input = '';
|
let _input = '';
|
||||||
let _error;
|
let _error;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setInput('');
|
setInput('');
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_input += decoder.decode(value);
|
_input += decoder.decode(value);
|
||||||
|
|
||||||
logger.trace('Set input', _input);
|
logger.trace('Set input', _input);
|
||||||
|
|
||||||
setInput(_input);
|
setInput(_input);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -56,10 +70,10 @@ export function usePromptEnhancer() {
|
|||||||
if (_error) {
|
if (_error) {
|
||||||
logger.error(_error);
|
logger.error(_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnhancingPrompt(false);
|
setEnhancingPrompt(false);
|
||||||
setPromptEnhanced(true);
|
setPromptEnhanced(true);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setInput(_input);
|
setInput(_input);
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ import { type ActionFunctionArgs } from '@remix-run/cloudflare';
|
|||||||
import { StreamingTextResponse, parseStreamPart } from 'ai';
|
import { StreamingTextResponse, parseStreamPart } from 'ai';
|
||||||
import { streamText } from '~/lib/.server/llm/stream-text';
|
import { streamText } from '~/lib/.server/llm/stream-text';
|
||||||
import { stripIndents } from '~/utils/stripIndent';
|
import { stripIndents } from '~/utils/stripIndent';
|
||||||
|
import type { StreamingOptions } from '~/lib/.server/llm/stream-text';
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
@ -11,14 +12,34 @@ export async function action(args: ActionFunctionArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
||||||
const { message } = await request.json<{ message: string }>();
|
const { message, model, provider, apiKeys } = await request.json<{
|
||||||
|
message: string;
|
||||||
|
model: string;
|
||||||
|
provider: string;
|
||||||
|
apiKeys?: Record<string, string>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// Validate 'model' and 'provider' fields
|
||||||
|
if (!model || typeof model !== 'string') {
|
||||||
|
throw new Response('Invalid or missing model', {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad Request'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider || typeof provider !== 'string') {
|
||||||
|
throw new Response('Invalid or missing provider', {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad Request'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await streamText(
|
const result = await streamText(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: stripIndents`
|
content: `[Model: ${model}]\n\n[Provider: ${provider}]\n\n` + stripIndents`
|
||||||
I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
|
I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
|
||||||
|
|
||||||
IMPORTANT: Only respond with the improved prompt and nothing else!
|
IMPORTANT: Only respond with the improved prompt and nothing else!
|
||||||
@ -30,28 +51,42 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
context.cloudflare.env,
|
context.cloudflare.env,
|
||||||
|
undefined,
|
||||||
|
apiKeys
|
||||||
);
|
);
|
||||||
|
|
||||||
const transformStream = new TransformStream({
|
const transformStream = new TransformStream({
|
||||||
transform(chunk, controller) {
|
transform(chunk, controller) {
|
||||||
const processedChunk = decoder
|
const text = decoder.decode(chunk);
|
||||||
.decode(chunk)
|
const lines = text.split('\n').filter(line => line.trim() !== '');
|
||||||
.split('\n')
|
|
||||||
.filter((line) => line !== '')
|
for (const line of lines) {
|
||||||
.map(parseStreamPart)
|
try {
|
||||||
.map((part) => part.value)
|
const parsed = parseStreamPart(line);
|
||||||
.join('');
|
if (parsed.type === 'text') {
|
||||||
|
controller.enqueue(encoder.encode(parsed.value));
|
||||||
controller.enqueue(encoder.encode(processedChunk));
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip invalid JSON lines
|
||||||
|
console.warn('Failed to parse stream part:', line);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const transformedStream = result.toAIStream().pipeThrough(transformStream);
|
const transformedStream = result.toAIStream().pipeThrough(transformStream);
|
||||||
|
|
||||||
return new StreamingTextResponse(transformedStream);
|
return new StreamingTextResponse(transformedStream);
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
||||||
|
if (error instanceof Error && error.message?.includes('API key')) {
|
||||||
|
throw new Response('Invalid or missing API key', {
|
||||||
|
status: 401,
|
||||||
|
statusText: 'Unauthorized'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw new Response(null, {
|
throw new Response(null, {
|
||||||
status: 500,
|
status: 500,
|
||||||
statusText: 'Internal Server Error',
|
statusText: 'Internal Server Error',
|
||||||
|
Loading…
Reference in New Issue
Block a user