mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
Merge cd4d27e998
into eda10b1212
This commit is contained in:
commit
a4d579f7c6
10
.env.template
Normal file
10
.env.template
Normal file
@ -0,0 +1,10 @@
|
||||
# ANTHROPIC
|
||||
ANTHROPIC_API_KEY=""
|
||||
|
||||
# AZURE AI
|
||||
AZURE_RESOURCE_NAME=""
|
||||
AZURE_RESOURCE_NAME_API_KEY=""
|
||||
|
||||
# RAKUTEN AI
|
||||
RAKUTEN_AI_GATEWAY_KEY=""
|
||||
RAKUTEN_AI_ANTHROPIC_URL=""
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -24,7 +24,8 @@ dist-ssr
|
||||
|
||||
/.cache
|
||||
/build
|
||||
.env*
|
||||
.env
|
||||
.env.local
|
||||
*.vars
|
||||
.wrangler
|
||||
_worker.bundle
|
||||
|
@ -21,7 +21,8 @@ export function Header() {
|
||||
<div className="flex items-center gap-2 z-logo text-bolt-elements-textPrimary cursor-pointer">
|
||||
<div className="i-ph:sidebar-simple-duotone text-xl" />
|
||||
<a href="/" className="text-2xl font-semibold text-accent flex items-center">
|
||||
<span className="i-bolt:logo-text?mask w-[46px] inline-block" />
|
||||
{/* <span className="i-bolt:logo-text?mask w-[46px] inline-block" /> */}
|
||||
<span className="ml-2">Flora</span>
|
||||
</a>
|
||||
</div>
|
||||
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
|
@ -16,6 +16,7 @@ import { cubicEasingFn } from '~/utils/easings';
|
||||
import { renderLogger } from '~/utils/logger';
|
||||
import { EditorPanel } from './EditorPanel';
|
||||
import { Preview } from './Preview';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
interface WorkspaceProps {
|
||||
chatStarted?: boolean;
|
||||
@ -52,6 +53,53 @@ const workbenchVariants = {
|
||||
},
|
||||
} satisfies Variants;
|
||||
|
||||
async function downloadAsZip(files: Record<string, any>) {
|
||||
const zip = new JSZip();
|
||||
|
||||
// add files to zip
|
||||
for (const [path, dirent] of Object.entries(files)) {
|
||||
if (dirent?.type === 'file' && dirent.content) {
|
||||
zip.file(path.startsWith('/') ? path.slice(1) : path, dirent.content);
|
||||
}
|
||||
}
|
||||
|
||||
// generate and download zip
|
||||
const blob = await zip.generateAsync({ type: 'blob' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'files.zip';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
async function copyFilesToDirectory(files: Record<string, any>) {
|
||||
try {
|
||||
// using the Fetch API to send files to your backend
|
||||
const response = await fetch('/api/copy-files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
files,
|
||||
targetDirectory: '/Users/yaqub.mahmoud/github/ai-website-microservice/web-projects/project_name',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to copy files');
|
||||
}
|
||||
|
||||
toast.success('Files copied successfully');
|
||||
} catch (error) {
|
||||
console.error('Error copying files:', error);
|
||||
toast.error('Failed to copy files');
|
||||
}
|
||||
}
|
||||
|
||||
export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => {
|
||||
renderLogger.trace('Workbench');
|
||||
|
||||
@ -132,6 +180,14 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
||||
Toggle Terminal
|
||||
</PanelHeaderButton>
|
||||
)}
|
||||
<PanelHeaderButton className="mr-1 text-sm" onClick={() => downloadAsZip(files)}>
|
||||
<div className="i-ph:download mr-1" />
|
||||
Download ZIP
|
||||
</PanelHeaderButton>
|
||||
{/* <PanelHeaderButton className="mr-1 text-sm" onClick={() => copyFilesToDirectory(files)}>
|
||||
<div className="i-ph:rocket mr-1" />
|
||||
Deploy
|
||||
</PanelHeaderButton> */}
|
||||
<IconButton
|
||||
icon="i-ph:x-circle"
|
||||
className="-mr-1"
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { streamText as _streamText, convertToCoreMessages } from 'ai';
|
||||
import { getAPIKey } from '~/lib/.server/llm/api-key';
|
||||
import { getAnthropicModel } from '~/lib/.server/llm/model';
|
||||
import { MAX_TOKENS } from './constants';
|
||||
import { getSystemPrompt } from './prompts';
|
||||
|
||||
import { createAnthropic } from '@ai-sdk/anthropic';
|
||||
import { createAzure } from '@ai-sdk/azure';
|
||||
import { createOllama } from 'ollama-ai-provider';
|
||||
|
||||
interface ToolResult<Name extends string, Args, Result> {
|
||||
toolCallId: string;
|
||||
toolName: Name;
|
||||
@ -22,14 +25,79 @@ export type Messages = Message[];
|
||||
export type StreamingOptions = Omit<Parameters<typeof _streamText>[0], 'model'>;
|
||||
|
||||
export function streamText(messages: Messages, env: Env, options?: StreamingOptions) {
|
||||
return _streamText({
|
||||
model: getAnthropicModel(getAPIKey(env)),
|
||||
system: getSystemPrompt(),
|
||||
maxTokens: MAX_TOKENS,
|
||||
headers: {
|
||||
'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15',
|
||||
},
|
||||
messages: convertToCoreMessages(messages),
|
||||
...options,
|
||||
});
|
||||
try {
|
||||
const azureResourceName = process.env.AZURE_RESOURCE_NAME;
|
||||
const azureResourceNameApiKey = process.env.AZURE_RESOURCE_NAME_API_KEY;
|
||||
const azure = createAzure({
|
||||
resourceName: azureResourceName,
|
||||
apiKey: azureResourceNameApiKey,
|
||||
headers: {
|
||||
api_version: '2024-11-20',
|
||||
},
|
||||
});
|
||||
|
||||
// `
|
||||
// For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
|
||||
// By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them.
|
||||
// Use icons from lucide-react for logos.
|
||||
// Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags.
|
||||
// `;
|
||||
|
||||
// return _streamText({
|
||||
// // model: azure('gpt-4o'),
|
||||
// model: azure('gpt-4o-2'),
|
||||
// system: getSystemPrompt(),
|
||||
// messages: convertToCoreMessages(messages),
|
||||
// maxTokens: 8192,
|
||||
// ...options,
|
||||
// });
|
||||
|
||||
const anthropic = createAnthropic({
|
||||
baseURL: process.env.RAKUTEN_AI_ANTHROPIC_URL,
|
||||
apiKey: 'test',
|
||||
});
|
||||
|
||||
return _streamText({
|
||||
model: anthropic('claude-3-7-sonnet-20250219'),
|
||||
system: getSystemPrompt(),
|
||||
maxTokens: 16384,
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.RAKUTEN_AI_GATEWAY_KEY}`,
|
||||
},
|
||||
messages: convertToCoreMessages(messages),
|
||||
...options,
|
||||
});
|
||||
|
||||
// const ollama = createOllama({
|
||||
// baseURL: 'http://localhost:11434/api',
|
||||
// });
|
||||
|
||||
// return _streamText({
|
||||
// // model: ollama('llava'),
|
||||
// // model: ollama('deepseek-r1:7b'),
|
||||
// model: ollama('deepseek-r1:70b'),
|
||||
// system: getSystemPrompt(),
|
||||
// messages: convertToCoreMessages(messages),
|
||||
// maxTokens: MAX_TOKENS,
|
||||
// ...options,
|
||||
// });
|
||||
|
||||
// const anthropic = createAnthropic({
|
||||
// apiKey: getAPIKey(env),
|
||||
// });
|
||||
|
||||
// return _streamText({
|
||||
// model: anthropic('claude-3-5-sonnet-20240620'),
|
||||
// system: getSystemPrompt(),
|
||||
// maxTokens: MAX_TOKENS,
|
||||
// headers: {
|
||||
// 'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15',
|
||||
// },
|
||||
// messages: convertToCoreMessages(messages),
|
||||
// ...options,
|
||||
// });
|
||||
} catch (error) {
|
||||
console.error('Error streaming text:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -34,13 +34,13 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
||||
|
||||
const result = await streamText(messages, context.cloudflare.env, options);
|
||||
|
||||
return stream.switchSource(result.toAIStream());
|
||||
return stream.switchSource(result.toDataStream());
|
||||
},
|
||||
};
|
||||
|
||||
const result = await streamText(messages, context.cloudflare.env, options);
|
||||
|
||||
stream.switchSource(result.toAIStream());
|
||||
stream.switchSource(result.toDataStream());
|
||||
|
||||
return new Response(stream.readable, {
|
||||
status: 200,
|
||||
|
@ -24,6 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^0.0.39",
|
||||
"@ai-sdk/azure": "^0.0.39",
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
@ -61,7 +62,9 @@
|
||||
"isbot": "^4.1.0",
|
||||
"istextorbinary": "^9.5.0",
|
||||
"jose": "^5.6.3",
|
||||
"jszip": "^3.10.1",
|
||||
"nanostores": "^0.10.3",
|
||||
"ollama-ai-provider": "^1.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
@ -81,13 +84,15 @@
|
||||
"@cloudflare/workers-types": "^4.20240620.0",
|
||||
"@remix-run/dev": "^2.10.0",
|
||||
"@types/diff": "^5.2.1",
|
||||
"@types/node": "^22.10.10",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-ci": "^3.0.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^3.3.2",
|
||||
"typescript": "^5.5.2",
|
||||
"sass-embedded": "^1.83.4",
|
||||
"typescript": "^5.7.3",
|
||||
"unified": "^11.0.5",
|
||||
"unocss": "^0.61.3",
|
||||
"vite": "^5.3.1",
|
||||
|
6685
pnpm-lock.yaml
6685
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user