chatgpt apiを使って、入力テキストからhtmlスライド(reveal.js)を自動生成して、プレビューも出来るようしてみた。(react + node)
nodeプロジェクト生成
1 2 3 4 |
mkdir chatgpt-backend cd chatgpt-backend npm init -y npm install express openai cors |
server.jsで、入力テキスト → chatgpt api → html出力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// server.js(Node.jsバックエンド) const express = require('express'); const cors = require('cors'); const { OpenAI } = require('openai'); const app = express(); const port = 3000; app.use(cors()); app.use(express.json()); const openai = new OpenAI({ apiKey: 'sk-' // ※ セキュリティのため .env に移行するのが望ましい }); app.post('/api/generate', async (req, res) => { const userText = req.body.text; try { const chatRes = await openai.chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'system', content: 'あなたはHTMLスライドを生成するアシスタントです。スライドにはアイコン、絵文字、タイトル、リスト、ビジュアル的強調(色や太字)なども適度に加えてください。htmlファイルとしてそのまま利用できる出力してください。' }, { role: 'user', content: `以下の内容をreveal.js形式のスライドHTMLにしてください。(視覚的にも引き込まれるように):\n${userText}` } ] }); const html = chatRes.choices[0].message.content; res.json({ html }); } catch (err) { console.error('エラー:', err); res.status(500).send('スライド生成中にエラーが発生しました'); } }); app.listen(port, () => { console.log(`✅ サーバー起動中:http://localhost:${port}`); }); |
バックエンドのnodeサーバ起動
1 |
node server.js |
フロントはreactで作成
1 2 3 |
npx create-react-app lightning-client cd lightning-client npm install |
src/App.jsに記述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
// Reactフロントエンド App.js(1ファイル構成) import { useState, useEffect, useRef } from "react"; function App() { const [inputText, setInputText] = useState(""); const [htmlText, setHtmlText] = useState(""); const [loading, setLoading] = useState(false); const iframeRef = useRef(null); const generateSlides = async () => { setLoading(true); try { const res = await fetch("http://localhost:3000/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: inputText }), }); const data = await res.json(); setHtmlText(data.html); } catch (error) { console.error("スライド生成失敗:", error); } finally { setLoading(false); } }; // iframeの高さを自動調整(オプション) useEffect(() => { if (iframeRef.current) { iframeRef.current.onload = () => { try { const iframeDoc = iframeRef.current.contentDocument || iframeRef.current.contentWindow.document; const height = iframeDoc.body.scrollHeight; iframeRef.current.style.height = height + 50 + 'px'; } catch (e) { console.warn("iframe自動高さ調整失敗:", e); } }; } }, [htmlText]); return ( <div style={{ padding: '20px', fontFamily: 'sans-serif' }}> <h1> 1分LTスライド自動生成</h1> <h2> 1. トーク内容入力</h2> <textarea value={inputText} onChange={(e) => setInputText(e.target.value)} rows={12} style={{ width: '100%', marginBottom: '10px' }} placeholder="エピソードや説明したい内容を入力..." /> <br /> <button onClick={generateSlides} disabled={loading}> {loading ? " 生成中..." : " スライドを生成"} </button> <h2 style={{ marginTop: '20px' }}>️ 2. HTML内容(修正可能)</h2> <textarea value={htmlText} onChange={(e) => setHtmlText(e.target.value)} rows={15} style={{ width: '100%', fontFamily: 'monospace', backgroundColor: '#f9f9f9' }} /> <h2 style={{ marginTop: '20px' }}>️ 3. スライドプレビュー</h2> <iframe ref={iframeRef} srcDoc={htmlText} style={{ width: '100%', height: '600px', border: '1px solid #ccc', backgroundColor: 'white', overflow: 'auto' }} sandbox="allow-scripts" title="スライドプレビュー" ></iframe> </div> ); } export default App; |
フロントエンドのreactサーバ起動
1 |
npm start |