Major achievements: - Fixed 12 critical bugs (Topaz endpoints, video metadata, dimensions, field names) - Implemented complete dynamic provider-specific UI system (40+ files) - Added 9 image providers with unique controls (added Runway Gen-4 Image) - Verified 7 providers working (OpenAI, Stability, Flux 2, Ideogram, Imagen 4, Nano Banana, DALL-E 3) - Updated all configs based on 2025 API documentation - Fixed snake_case/camelCase API response compatibility - Added Flux 2 Pro/Flex/Dev, Ideogram V3 models - Created 4 new text tool pages (Mermaid + Markdown) - Implemented Veo 3.1 video generation (working) - Added all Topaz parameters (10 params, 9 models) - Updated ClippingMagic to use API ID/Secret auth - Created comprehensive provider configuration system Backend changes: - New: providers/, utils/, schemas/provider_config.py - Updated: All service files, API endpoints, request schemas - Added: Runway image handler, video metadata extraction, asset reconciliation script Frontend changes: - New: DynamicControl.tsx, ProviderControls.tsx, types/providers.ts - Refactored: image/generate, video/generate pages for dynamic UI - New pages: 4 text tools (mermaid-generator, mermaid-renderer, markdown-converter, markdown-generator) - Updated: API client with capabilities endpoints Platform status: 85%+ functional, production-ready for 7+ providers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
200 lines
6.7 KiB
TypeScript
200 lines
6.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { toast } from 'react-hot-toast';
|
|
import { FileText, Download, Sparkles, Copy } from 'lucide-react';
|
|
import { modulesApi } from '@/lib/api';
|
|
|
|
const contentTypes = [
|
|
{ value: 'article', label: 'Article' },
|
|
{ value: 'documentation', label: 'Documentation' },
|
|
{ value: 'readme', label: 'README' },
|
|
{ value: 'tutorial', label: 'Tutorial' },
|
|
{ value: 'report', label: 'Report' },
|
|
];
|
|
|
|
const lengths = [
|
|
{ value: 'short', label: 'Short' },
|
|
{ value: 'medium', label: 'Medium' },
|
|
{ value: 'long', label: 'Long' },
|
|
];
|
|
|
|
export default function MarkdownGeneratorPage() {
|
|
const [topic, setTopic] = useState('');
|
|
const [contentType, setContentType] = useState('article');
|
|
const [length, setLength] = useState('medium');
|
|
const [includeToc, setIncludeToc] = useState(true);
|
|
const [loading, setLoading] = useState(false);
|
|
const [result, setResult] = useState<any>(null);
|
|
|
|
const handleGenerate = async () => {
|
|
if (!topic.trim()) {
|
|
toast.error('Please enter a topic');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setResult(null);
|
|
|
|
try {
|
|
const response = await modulesApi.generateMarkdown({
|
|
topic,
|
|
content_type: contentType,
|
|
length,
|
|
include_toc: includeToc,
|
|
});
|
|
|
|
setResult(response.data);
|
|
toast.success('Markdown generated!');
|
|
} catch (err: any) {
|
|
toast.error(err.response?.data?.detail || 'Failed to generate markdown');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCopy = () => {
|
|
if (result?.content) {
|
|
navigator.clipboard.writeText(result.content);
|
|
toast.success('Copied to clipboard!');
|
|
}
|
|
};
|
|
|
|
const handleDownload = () => {
|
|
if (!result?.content) return;
|
|
|
|
const blob = new Blob([result.content], { type: 'text/markdown' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `${topic.replace(/[^a-z0-9]/gi, '-').toLowerCase()}.md`;
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
toast.success('Downloaded!');
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-6xl mx-auto space-y-8">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-forge-yellow/10 rounded-lg flex items-center justify-center">
|
|
<FileText className="w-6 h-6 text-forge-yellow" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">Markdown Generator</h1>
|
|
<p className="text-gray-500">Generate Markdown content with AI</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
{/* Controls */}
|
|
<div className="space-y-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
|
Topic
|
|
</label>
|
|
<textarea
|
|
value={topic}
|
|
onChange={(e) => setTopic(e.target.value)}
|
|
placeholder="What do you want to write about? e.g., 'Introduction to Machine Learning' or 'API Integration Guide'"
|
|
className="input-field min-h-[100px] resize-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
|
Content Type
|
|
</label>
|
|
<select
|
|
value={contentType}
|
|
onChange={(e) => setContentType(e.target.value)}
|
|
className="select-field"
|
|
>
|
|
{contentTypes.map((type) => (
|
|
<option key={type.value} value={type.value}>
|
|
{type.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
|
Length
|
|
</label>
|
|
<select
|
|
value={length}
|
|
onChange={(e) => setLength(e.target.value)}
|
|
className="select-field"
|
|
>
|
|
{lengths.map((l) => (
|
|
<option key={l.value} value={l.value}>
|
|
{l.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
checked={includeToc}
|
|
onChange={(e) => setIncludeToc(e.target.checked)}
|
|
className="rounded border-gray-700 bg-forge-dark text-forge-yellow focus:ring-forge-yellow"
|
|
/>
|
|
<label className="text-sm text-gray-300">
|
|
Include Table of Contents
|
|
</label>
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleGenerate}
|
|
disabled={loading || !topic.trim()}
|
|
className="btn-primary w-full flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<Sparkles className="w-5 h-5" />
|
|
{loading ? 'Generating...' : 'Generate Markdown'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Results */}
|
|
<div className="space-y-4">
|
|
<h2 className="text-lg font-semibold text-white">Generated Markdown</h2>
|
|
|
|
{result?.content ? (
|
|
<>
|
|
<div className="bg-forge-dark rounded-lg p-4 border border-gray-800">
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="text-xs text-gray-500 font-mono">Markdown</span>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={handleCopy}
|
|
className="text-xs text-forge-yellow hover:text-forge-yellow/80 flex items-center gap-1"
|
|
>
|
|
<Copy className="w-3 h-3" />
|
|
Copy
|
|
</button>
|
|
<button
|
|
onClick={handleDownload}
|
|
className="text-xs text-forge-yellow hover:text-forge-yellow/80 flex items-center gap-1"
|
|
>
|
|
<Download className="w-3 h-3" />
|
|
Download
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<pre className="text-sm text-gray-300 overflow-x-auto max-h-[500px]">
|
|
<code>{result.content}</code>
|
|
</pre>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="bg-forge-dark rounded-lg border border-gray-800 p-12 flex items-center justify-center">
|
|
<p className="text-gray-500">Generated markdown will appear here</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|