This commit is contained in:
Yaqub Mahmoud 2025-05-02 03:49:50 +00:00 committed by GitHub
commit a4d579f7c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 3811 additions and 3047 deletions

10
.env.template Normal file
View 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
View File

@ -24,7 +24,8 @@ dist-ssr
/.cache
/build
.env*
.env
.env.local
*.vars
.wrangler
_worker.bundle

View File

@ -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">

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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",

File diff suppressed because it is too large Load Diff