始めに
今回はいよいよWindsurfを使ってstep5をやっていきます!
プロンプトを投げてみる
投げるプロンプトは以下を考えました。
抜けや漏れが無いか心配ですが、できるだけここまでの流れをWindsurf先輩に読み取ってもらえるように記載しております。
*********************
このプロジェクトはPython(Flask)製のニュースダイジェストWebアプリです。
Cursorで開発していたものをWindsurfに引き継いで、続きを進めたいです。
【プロジェクト概要】
毎朝6時に自動でAIニュースを取得・要約するWebサイト。
RSSフィード(ITmedia・Yahoo!ニュース)から記事を取得し、
Gemini API(gemini-2.5-flash)でsummaryとtermsをStructured Outputsで生成。
日付ごとにJSONファイルで保存する構成。
【現在の完了状況】
- Step 1完了:Flaskでトップページ表示(ダミーJSONでレイアウト確認済み)
- Step 2完了:feedparserでRSS取得・フィルタリング・JSONに保存・90日自動削除
- Step 3完了:Gemini + Structured OutputsでsummaryとtermsをJSON生成
- Step 4完了:ログ付き一括実行スクリプト・Windowsタスクスケジューラで毎朝6時に自動実行
【環境】
- Python 3.14.2
- 仮想環境は使わない(グローバル環境)
- OS: Windows
- 起動は python run.py
- バッチとWebは分離済み(バッチでJSON生成、Flaskでデータ表示)
【開発方針】
- MVPを優先。まず動くものを作り、うまくいったら拡張する
- 過剰な実装・先回りした設計は不要
- コメントは日本語で書く
- Gemini APIは無料枠のためリクエスト数は最小限に抑える
【今回やりたいこと:読み上げ機能の実装】
static/js/speech.js にWeb Speech APIを使った読み上げ機能を実装したい。
- 「読み上げる」ボタンと「止める」ボタンをUIに追加
- 読み上げ対象はsummaryのテキスト
- 言語は ja-JP、速度はデフォルト(1.0)
まず現在のプロジェクト構成を確認してから、実装計画を提示してください。
いきなりコードを書かずに、計画を先に見せてください。
*********************
結果は……
まず、static/js/(読み上げ機能のJavaScriptを置く場所) が存在しないことを自動で把握してくれたようです。
Cursorでも似たことはできますが、Windsurfは何も指定しなくても最初にプロジェクト全体を読みに行くのがデフォルトの動きらしいです。
確認完了後、以下の実装計画が提示されました。
【Step 1: ディレクトリ・ファイル作成】
-
static/js/ディレクトリを作成 -
static/js/speech.jsを新規作成
【Step 2: speech.js の実装内容】
-
speechSynthesisAPIを使用 - 読み上げ開始・停止関数を実装(
ja-JP・速度デフォルト1.0) - 二重読み上げ防止のための状態管理
【Step 3: HTMLテンプレート修正】
-
templates/index.htmlの各記事カードに「読み上げる」「止める」ボタンを追加 -
data-summary属性でsummaryテキストを渡し、クリックイベントで関数を呼び出す
【Step 4: base.html 修正】
-
</body>の前にspeech.jsの読み込みタグを追加
【Step 5: CSS調整(必要に応じて)】
- 既存デザインに合わせたボタンスタイルの調整
計画通りに進めてもらう
『この計画で進めてよろしいでしょうか?』と聞かれていたので、「OK!」と返したところ、一度 Permission denied エラーが出ました。
Windsurfがフォルダを自動で作ろうとして権限で弾かれた形です。
ターミナルで手動で static/js/ フォルダを作ります。
cd C:\dev\news-digest
mkdir static\js
その後「続けてください」と伝えたら、以下の3ファイルを一気に仕上げてくれました。
-
static/js/speech.js:Web Speech APIによる読み上げ・停止・状態管理 -
templates/index.html:各記事カードに「読み上げる」「止める」ボタンを追加 -
templates/base.html:speech.jsの読み込みを追加
あとは python run.py で起動して動作確認するだけです。
実際に動作確認を実施したところ……。
確かに読み上げボタンが作られましたね!!
でも、各記事カードの『読み上げる』と『止める』は微妙かもしれませんね。
忙しいときに、ボタン一つでその日のニュースを全部読み上げて欲しいし。
あと、実際にきちんと読み上げてくれましたが、速度がゆっくりすぎました。
これを改善点としてお知らせします。
【投げたプロンプト】
*****************************
2点修正をお願いします。
① 読み上げボタンの仕様変更
現在は各記事カードに「読み上げる」「止める」ボタンがついていますが、ページ上部に1つだけボタンを置いて、その日の全記事のsummaryを上から順番に続けて読み上げる形に変えてください。
② 読み上げ速度の変更
現在 rate = 1.0 ですが、1.5 に変更してください。
*****************************
あっという間に変更してくれました。
変更内容はこちらです。
- 各記事カードからボタンを削除
- ページ上部に「全記事を読み上げる」「止める」ボタンを追加
- 全記事のsummaryを上から順番に連続読み上げする機能を実装
- rate を 1.5 に変更
なお、リントエラーが出ていましたが、Jinja2テンプレート構文をJavaScriptリンターが誤解釈しているだけで、Flaskアプリとしては正しく動作するとのこと。
【リントエラーとは】
プログラムを実行する前に「このコードに変な書き方はないか?」を自動でチェックしてくれる機能で、Windsurfが裏側で常に動かしています。
今回の場合、JavaScriptのチェックツール(リンター)が {{ article.summary }} というFlask特有の書き方を見て「これJavaScriptとして変だぞ」と警告を出していました。
ただし、『全記事を読み上げる』をクリックしても読み上げされなくなってしまった……。
ですのでそれをそのまま伝えてみました。
すると以下の通り結果が返ってきて、無事に解決。
原因はJinja2テンプレートからJavaScriptへのデータの渡し方にあったようです。
修正内容はこちら。
- onclick属性の代わりにaddEventListenerを使用
- JavaScript配列を直接生成してsummaryデータを渡す方式に変更
- 記事がある場合のみボタンを表示するように修正
【備忘録】
onclickはHTMLのタグの中に直接「クリックしたらこれを実行して」と書く古い方法addEventListenerはJavaScriptのファイル側で「このボタンがクリックされたら〜」と別で書く今どきの方法。
HTMLとJavaScriptを分離できるので、Jinja2の {{ }} が混ざらなくなる。
FlaskのHTMLテンプレートの中に{{ article.summary }}と書くと、JavaScriptのリンターが「これ何?」と混乱することがあります。なので、HTMLが読み込まれるときにPythonのデータをJavaScriptの配列として先に変換しておく、という方法に変えてくれたということのようです。
python run.py で再起動してブラウザをリロードしたら、無事に読み上げが動きました!!
次回
仕上げを行い、MVPを完成させます!!

