Skip to main content
Glama
framework-adapter.ts•17 kB
import { GeminiHelper } from '../utils/gemini-helper.js'; // Framework-specific adapters for cross-platform support export interface FrameworkAdapter { name: string; fileExtensions: string[]; componentSyntax: ComponentSyntax; stylingMethod: 'classes' | 'styled-components' | 'css-modules' | 'emotion'; convertComponent(html: string, options?: ConversionOptions): Promise<string>; generateProject(config: ProjectConfig): Promise<ProjectStructure>; optimizeForFramework(code: string): Promise<string>; } export interface ComponentSyntax { componentWrapper: string; propsInterface: string; stateManagement: string; eventHandling: string; conditionalRendering: string; listRendering: string; } export interface ConversionOptions { includeTypes?: boolean; useCompositionAPI?: boolean; addAccessibility?: boolean; optimizeBundle?: boolean; targetVersion?: string; } export interface ProjectConfig { name: string; framework: string; features: string[]; buildTool: 'vite' | 'webpack' | 'rollup' | 'parcel' | 'esbuild'; cssFramework: 'tailwind' | 'bootstrap' | 'bulma' | 'material-ui'; stateManagement?: 'redux' | 'zustand' | 'pinia' | 'mobx' | 'recoil'; routing?: 'react-router' | 'vue-router' | 'reach-router' | 'next-router'; } export interface ProjectStructure { files: ProjectFile[]; dependencies: Record<string, string>; scripts: Record<string, string>; config: Record<string, any>; } export interface ProjectFile { path: string; content: string; type: 'component' | 'config' | 'style' | 'test' | 'doc'; } // React Adapter export class ReactAdapter implements FrameworkAdapter { name = 'React'; fileExtensions = ['.jsx', '.tsx']; stylingMethod: 'classes' = 'classes'; componentSyntax: ComponentSyntax = { componentWrapper: 'function Component(props) { return (...); }', propsInterface: 'interface Props { ... }', stateManagement: 'const [state, setState] = useState(...)', eventHandling: 'onClick={handleClick}', conditionalRendering: '{condition && <element />}', listRendering: '{items.map(item => <Item key={item.id} />)}' }; async convertComponent(html: string, options: ConversionOptions = {}): Promise<string> { const gemini = new GeminiHelper(); try { const prompt = `Convert this HTML with Tailwind CSS to a React component: ${html} Requirements: - Use TypeScript: ${options.includeTypes !== false} - Add accessibility: ${options.addAccessibility !== false} - Optimize for performance: ${options.optimizeBundle !== false} - Target React version: ${options.targetVersion || '18'} Generate: 1. Proper React component with hooks 2. TypeScript interfaces if enabled 3. Proper event handlers 4. Accessibility attributes 5. Performance optimizations`; const reactCode = await gemini.generateContent(prompt); return this.formatReactComponent(reactCode, options); } catch (error) { console.error('React conversion failed:', error); return this.fallbackReactComponent(html, options); } } async generateProject(config: ProjectConfig): Promise<ProjectStructure> { const files: ProjectFile[] = []; // Package.json files.push({ path: 'package.json', type: 'config', content: JSON.stringify({ name: config.name, version: '0.1.0', private: true, dependencies: { 'react': '^18.2.0', 'react-dom': '^18.2.0', '@types/react': '^18.2.0', '@types/react-dom': '^18.2.0', 'tailwindcss': '^3.4.0', 'autoprefixer': '^10.4.0', 'postcss': '^8.4.0', ...(config.stateManagement === 'redux' && { '@reduxjs/toolkit': '^2.0.0', 'react-redux': '^9.0.0' }), ...(config.routing === 'react-router' && { 'react-router-dom': '^6.8.0' }) }, devDependencies: { '@vitejs/plugin-react': '^4.2.0', 'vite': '^5.0.0', 'typescript': '^5.2.0', 'eslint': '^8.55.0', '@typescript-eslint/eslint-plugin': '^6.0.0', '@typescript-eslint/parser': '^6.0.0' }, scripts: { 'dev': 'vite', 'build': 'tsc && vite build', 'lint': 'eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0', 'preview': 'vite preview' } }, null, 2) }); // Vite config files.push({ path: 'vite.config.ts', type: 'config', content: `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], resolve: { alias: { '@': '/src' } } })` }); // Tailwind config files.push({ path: 'tailwind.config.js', type: 'config', content: `/** @type {import('tailwindcss').Config} */ export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], }` }); // Main App component files.push({ path: 'src/App.tsx', type: 'component', content: `import React from 'react' import './App.css' function App() { return ( <div className="min-h-screen bg-gray-50"> <header className="bg-white shadow"> <div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <h1 className="text-3xl font-bold text-gray-900"> ${config.name} </h1> </div> </header> <main> <div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8"> <div className="px-4 py-6 sm:px-0"> <div className="border-4 border-dashed border-gray-200 rounded-lg h-96 flex items-center justify-center"> <p className="text-gray-500">Welcome to your new React app!</p> </div> </div> </div> </main> </div> ) } export default App` }); return { files, dependencies: {}, scripts: {}, config: {} }; } async optimizeForFramework(code: string): Promise<string> { // React-specific optimizations return code .replace(/className="([^"]*?)"/g, (match, classes) => { // Optimize class names for React const optimized = classes.split(' ').filter(Boolean).join(' '); return `className="${optimized}"`; }) .replace(/onClick="([^"]*?)"/g, 'onClick={$1}') // Convert to proper React event handlers .replace(/<(\w+)([^>]*?)\/>/g, '<$1$2 />'); // Ensure self-closing tags } private formatReactComponent(code: string, options: ConversionOptions): string { let formatted = code; if (options.includeTypes !== false) { formatted = `import React from 'react';\n\n${formatted}`; } return formatted; } private fallbackReactComponent(html: string, options: ConversionOptions): string { const componentName = 'GeneratedComponent'; return `import React from 'react'; ${options.includeTypes !== false ? `interface ${componentName}Props { className?: string; children?: React.ReactNode; }` : ''} export const ${componentName}${options.includeTypes !== false ? ': React.FC<' + componentName + 'Props>' : ''} = ({ className, children, ...props }) => { return ( <div className={\`\${className || ''}\`} {...props}> {/* Converted from HTML */} ${html.replace(/class=/g, 'className=')} {children} </div> ); }; export default ${componentName};`; } } // Vue Adapter export class VueAdapter implements FrameworkAdapter { name = 'Vue'; fileExtensions = ['.vue']; stylingMethod: 'classes' = 'classes'; componentSyntax: ComponentSyntax = { componentWrapper: '<template>...</template><script setup>...</script>', propsInterface: 'interface Props { ... }', stateManagement: 'const state = ref(...)', eventHandling: '@click="handleClick"', conditionalRendering: 'v-if="condition"', listRendering: 'v-for="item in items" :key="item.id"' }; async convertComponent(html: string, options: ConversionOptions = {}): Promise<string> { const gemini = new GeminiHelper(); try { const prompt = `Convert this HTML with Tailwind CSS to a Vue 3 component: ${html} Requirements: - Use Composition API: ${options.useCompositionAPI !== false} - Use TypeScript: ${options.includeTypes !== false} - Add accessibility: ${options.addAccessibility !== false} - Target Vue version: ${options.targetVersion || '3'} Generate: 1. Single File Component (.vue) 2. Composition API with <script setup> 3. TypeScript support if enabled 4. Proper Vue directives 5. Reactive state management`; const vueCode = await gemini.generateContent(prompt); return this.formatVueComponent(vueCode, options); } catch (error) { console.error('Vue conversion failed:', error); return this.fallbackVueComponent(html, options); } } async generateProject(config: ProjectConfig): Promise<ProjectStructure> { const files: ProjectFile[] = []; // Package.json for Vue files.push({ path: 'package.json', type: 'config', content: JSON.stringify({ name: config.name, version: '0.1.0', private: true, dependencies: { 'vue': '^3.4.0', '@vitejs/plugin-vue': '^5.0.0', 'tailwindcss': '^3.4.0', 'autoprefixer': '^10.4.0', 'postcss': '^8.4.0', ...(config.stateManagement === 'pinia' && { 'pinia': '^2.1.0' }), ...(config.routing === 'vue-router' && { 'vue-router': '^4.2.0' }) }, devDependencies: { 'vite': '^5.0.0', 'typescript': '^5.2.0', 'vue-tsc': '^1.8.0', '@vue/tsconfig': '^0.5.0' }, scripts: { 'dev': 'vite', 'build': 'vue-tsc && vite build', 'preview': 'vite preview' } }, null, 2) }); // Vue-specific App component files.push({ path: 'src/App.vue', type: 'component', content: `<template> <div class="min-h-screen bg-gray-50"> <header class="bg-white shadow"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <h1 class="text-3xl font-bold text-gray-900"> ${config.name} </h1> </div> </header> <main> <div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8"> <div class="px-4 py-6 sm:px-0"> <div class="border-4 border-dashed border-gray-200 rounded-lg h-96 flex items-center justify-center"> <p class="text-gray-500">Welcome to your new Vue app!</p> </div> </div> </div> </main> </div> </template> <script setup lang="ts"> import { ref } from 'vue' // Component logic here </script> <style scoped> /* Component-specific styles */ </style>` }); return { files, dependencies: {}, scripts: {}, config: {} }; } async optimizeForFramework(code: string): Promise<string> { // Vue-specific optimizations return code .replace(/onClick=/g, '@click=') .replace(/onChange=/g, '@change=') .replace(/className=/g, 'class=') .replace(/htmlFor=/g, 'for='); } private formatVueComponent(code: string, options: ConversionOptions): string { return code; } private fallbackVueComponent(html: string, options: ConversionOptions): string { return `<template> <div> ${html} </div> </template> <script setup${options.includeTypes !== false ? ' lang="ts"' : ''}> import { ref } from 'vue' // Component logic </script> <style scoped> /* Component styles */ </style>`; } } // Svelte Adapter export class SvelteAdapter implements FrameworkAdapter { name = 'Svelte'; fileExtensions = ['.svelte']; stylingMethod: 'classes' = 'classes'; componentSyntax: ComponentSyntax = { componentWrapper: '<script>...</script><main>...</main>', propsInterface: 'export let prop: type', stateManagement: 'let state = $state(...)', eventHandling: 'on:click={handleClick}', conditionalRendering: '{#if condition}...{/if}', listRendering: '{#each items as item (item.id)}...{/each}' }; async convertComponent(html: string, options: ConversionOptions = {}): Promise<string> { const gemini = new GeminiHelper(); try { const prompt = `Convert this HTML with Tailwind CSS to a Svelte component: ${html} Requirements: - Use TypeScript: ${options.includeTypes !== false} - Add accessibility: ${options.addAccessibility !== false} - Use Svelte 5 runes: ${options.targetVersion === '5'} Generate: 1. Svelte component (.svelte) 2. TypeScript support if enabled 3. Proper Svelte directives 4. Reactive state management 5. Event handling`; const svelteCode = await gemini.generateContent(prompt); return this.formatSvelteComponent(svelteCode, options); } catch (error) { console.error('Svelte conversion failed:', error); return this.fallbackSvelteComponent(html, options); } } async generateProject(config: ProjectConfig): Promise<ProjectStructure> { const files: ProjectFile[] = []; // Package.json for Svelte files.push({ path: 'package.json', type: 'config', content: JSON.stringify({ name: config.name, version: '0.1.0', private: true, dependencies: { 'svelte': '^4.2.0', '@sveltejs/vite-plugin-svelte': '^3.0.0', 'tailwindcss': '^3.4.0', 'autoprefixer': '^10.4.0', 'postcss': '^8.4.0' }, devDependencies: { 'vite': '^5.0.0', 'typescript': '^5.2.0', 'svelte-check': '^3.6.0', '@tsconfig/svelte': '^5.0.0' }, scripts: { 'dev': 'vite', 'build': 'vite build', 'preview': 'vite preview', 'check': 'svelte-check --tsconfig ./tsconfig.json' } }, null, 2) }); return { files, dependencies: {}, scripts: {}, config: {} }; } async optimizeForFramework(code: string): Promise<string> { // Svelte-specific optimizations return code .replace(/onClick=/g, 'on:click=') .replace(/onChange=/g, 'on:change=') .replace(/className=/g, 'class='); } private formatSvelteComponent(code: string, options: ConversionOptions): string { return code; } private fallbackSvelteComponent(html: string, options: ConversionOptions): string { return `<script${options.includeTypes !== false ? ' lang="ts"' : ''}> // Component logic </script> <main> ${html} </main> <style> /* Component styles */ </style>`; } } // Angular Adapter export class AngularAdapter implements FrameworkAdapter { name = 'Angular'; fileExtensions = ['.component.ts', '.component.html']; stylingMethod: 'classes' = 'classes'; componentSyntax: ComponentSyntax = { componentWrapper: '@Component({ ... }) export class Component { }', propsInterface: '@Input() prop: type', stateManagement: 'private state = signal(...)', eventHandling: '(click)="handleClick()"', conditionalRendering: '*ngIf="condition"', listRendering: '*ngFor="let item of items; trackBy: trackByFn"' }; async convertComponent(html: string, options: ConversionOptions = {}): Promise<string> { const componentName = 'GeneratedComponent'; const template = html .replace(/class=/g, 'class=') .replace(/onClick=/g, '(click)=') .replace(/onChange=/g, '(change)='); const component = `import { Component } from '@angular/core'; @Component({ selector: 'app-generated', template: \` ${template} \`, styleUrls: ['./generated.component.css'] }) export class ${componentName} { constructor() {} }`; return component; } async generateProject(config: ProjectConfig): Promise<ProjectStructure> { // Angular project structure would be more complex return { files: [], dependencies: {}, scripts: {}, config: {} }; } async optimizeForFramework(code: string): Promise<string> { return code .replace(/className=/g, 'class=') .replace(/onClick=/g, '(click)=') .replace(/onChange=/g, '(change)='); } } // Factory for creating adapters export class AdapterFactory { private static adapters: Map<string, FrameworkAdapter> = new Map([ ['react', new ReactAdapter()], ['vue', new VueAdapter()], ['svelte', new SvelteAdapter()], ['angular', new AngularAdapter()] ]); static getAdapter(framework: string): FrameworkAdapter | null { return this.adapters.get(framework.toLowerCase()) || null; } static getSupportedFrameworks(): string[] { return Array.from(this.adapters.keys()); } static registerAdapter(name: string, adapter: FrameworkAdapter): void { this.adapters.set(name.toLowerCase(), adapter); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tai-DT/mcp-tailwind-gemini'

If you have feedback or need assistance with the MCP directory API, please join our Discord server