Merge pull request #12 from vgcman16/codex/add-import-from-figma-feature

Add Figma import feature
This commit is contained in:
vgcman16 2025-06-05 18:48:00 -05:00 committed by GitHub
commit 609715eef3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 193 additions and 0 deletions

View File

@ -90,3 +90,23 @@ This implementation adds persistent file and folder locking functionality to the
2. **Contextual Icons**: Different icons and colors for different types of alerts
3. **Improved Error Details**: Better formatting of error details with monospace font and left border
4. **Responsive Buttons**: Better positioned and styled buttons with appropriate hover effects
# Built-in Figma Import Feature
## Overview
This update adds the ability to import a Figma URL directly into BoltDIY. The application fetches the Figma file and generates starter React component files for each top-level frame, creating a new chat with the generated code ready for editing.
## New Files
### 1. `app/utils/figmaImport.ts`
- Fetches Figma files and converts frames to simple React components
### 2. `app/components/figma/FigmaImport.client.tsx`
- Client component that imports a Figma design using query parameters
### 3. `app/routes/figma.tsx`
- Route to initiate the Figma import flow

View File

@ -96,6 +96,7 @@ project, please check the [project management guide](./PROJECT.md) to get starte
- ⬜ Granite Integration
- ✅ Popout Window for Web Container(@stijnus)
- ✅ Ability to change Popout window size (@stijnus)
- ✅ Import from Figma (@open-source)
## Features
@ -107,6 +108,7 @@ project, please check the [project management guide](./PROJECT.md) to get starte
- **Download projects as ZIP** for easy portability Sync to a folder on the host.
- **Integration-ready Docker support** for a hassle-free setup.
- **Deploy** directly to **Netlify**, **Vercel**, or **Cloudflare Pages**
- **Import Figma designs** to generate starter UI code.
## Setup

View File

@ -0,0 +1,58 @@
import { useSearchParams } from '@remix-run/react';
import { useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { BaseChat } from '~/components/chat/BaseChat';
import { Chat } from '~/components/chat/Chat.client';
import { useChatHistory } from '~/lib/persistence';
import { createChatFromFigma } from '~/utils/figmaImport';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
import { toast } from 'react-toastify';
export function FigmaImport() {
const [searchParams] = useSearchParams();
const { ready, importChat } = useChatHistory();
const [imported, setImported] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!ready || imported) {
return;
}
const url = searchParams.get('url');
const token = searchParams.get('token');
if (!url || !token) {
window.location.href = '/';
return;
}
createChatFromFigma(url, token)
.then(async (messages) => {
if (importChat) {
const name = url.split('/').slice(-1)[0];
await importChat(`Figma:${name}`, messages);
}
})
.catch((error) => {
console.error('Error importing Figma design:', error);
toast.error('Failed to import Figma design');
window.location.href = '/';
})
.finally(() => {
setLoading(false);
setImported(true);
});
}, [ready, imported, searchParams, importChat]);
return (
<ClientOnly fallback={<BaseChat />}>
{() => (
<>
<Chat />
{loading && <LoadingOverlay message="Importing Figma design..." />}
</>
)}
</ClientOnly>
);
}

28
app/routes/figma.tsx Normal file
View File

@ -0,0 +1,28 @@
import { json } from '@remix-run/cloudflare';
import type { LoaderFunctionArgs, MetaFunction } from '@remix-run/cloudflare';
import { ClientOnly } from 'remix-utils/client-only';
import { BaseChat } from '~/components/chat/BaseChat';
import { FigmaImport } from '~/components/figma/FigmaImport.client';
import { Header } from '~/components/header/Header';
import BackgroundRays from '~/components/ui/BackgroundRays';
export const meta: MetaFunction = () => {
return [
{ title: 'Bolt' },
{ name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' },
];
};
export async function loader(args: LoaderFunctionArgs) {
return json({ url: args.params.url });
}
export default function FigmaPage() {
return (
<div className="flex flex-col h-full w-full bg-bolt-elements-background-depth-1">
<BackgroundRays />
<Header />
<ClientOnly fallback={<BaseChat />}>{() => <FigmaImport />}</ClientOnly>
</div>
);
}

84
app/utils/figmaImport.ts Normal file
View File

@ -0,0 +1,84 @@
// Utility to import Figma designs and convert them to basic React components
import type { Message } from 'ai';
import { generateId } from './fileUtils';
import { escapeBoltTags } from './projectCommands';
/**
* Extract the file key from a Figma URL
*/
function extractFileKey(url: string): string {
const match = url.match(/file\/(\w+)/);
if (!match) {
throw new Error('Invalid Figma URL');
}
return match[1];
}
interface GeneratedComponent {
name: string;
code: string;
}
/**
* Recursively collect frame nodes from the Figma document
*/
function collectFrames(node: any, frames: any[]) {
if (node.type === 'FRAME') {
frames.push(node);
}
if (Array.isArray(node.children)) {
node.children.forEach((child) => collectFrames(child, frames));
}
}
function figmaFramesToComponents(frames: any[]): GeneratedComponent[] {
return frames.map((frame) => {
const name = frame.name.replace(/[^a-zA-Z0-9]/g, '') || 'Component';
const code = `export function ${name}() {\n return <div>${frame.name}</div>;\n}`;
return { name, code };
});
}
/**
* Create chat messages from a Figma design
*/
export async function createChatFromFigma(
figmaUrl: string,
token: string,
): Promise<Message[]> {
const fileKey = extractFileKey(figmaUrl);
const res = await fetch(`https://api.figma.com/v1/files/${fileKey}`, {
headers: { 'X-Figma-Token': token },
});
if (!res.ok) {
throw new Error(`Failed to fetch Figma file: ${res.status}`);
}
const data = await res.json();
const frames: any[] = [];
collectFrames(data.document, frames);
const components = figmaFramesToComponents(frames);
const componentActions = components
.map(
(c) =>
`<boltAction type="file" filePath="app/components/${c.name}.tsx">${escapeBoltTags(c.code)}</boltAction>`,
)
.join('\n');
const assistantMessage: Message = {
role: 'assistant',
content: `Converted Figma design from ${figmaUrl} to React components.\n<boltArtifact id="figma-components" title="Figma Components" type="bundled">${componentActions}</boltArtifact>`,
id: generateId(),
createdAt: new Date(),
};
const userMessage: Message = {
role: 'user',
content: `Import design from ${figmaUrl}`,
id: generateId(),
createdAt: new Date(),
};
return [userMessage, assistantMessage];
}

View File

@ -41,6 +41,7 @@ Also [this pinned post in our community](https://thinktank.ottomator.ai/t/videos
- **Download projects as ZIP** for easy portability.
- **Integration-ready Docker support** for a hassle-free setup.
- **One-click deployment** to **Netlify**, **Vercel**, or **Cloudflare Pages**.
- **Import Figma designs** to bootstrap your UI code.
---