📰

架空世界のニュースメディアを作ってみた(Gemini 1.5 Pro)

2024/10/22に公開

どうも、ネチコヤン などの面白ペット記事に癒されてる個人開発者です。
でも、現実世界のニュース記事ってネガティブな内容が多いですよね。
もっとコミカルなニュースが増えればいいのに...

...と思ったので、量産体制を構築してみることに

今回は使い慣れた Ruby on Rails をフルスタックモードで rails new
ERBアレルギーなので、 view は全て gem view_componentxxx_component.rb のみで実装する変態構成に。

view_componentの変態構成
class Themes::ArticlesController < ApplicationController
  def show
    # 一部省略
    render_cache_page(Views::Themes::Articles::ShowComponent.new(article:, articles:))
  end
end

class Views::Themes::Articles::ShowComponent < Views::BaseComponent
  def initialize(article:, articles:)
    super
    @article = article
    @articles = articles
  end

  def call
    content_tag(:section, class: "outer_container") do
      concat(content_tag(:section, class: "inner_container") do
        concat(breadcrumbs)

        concat(content_tag(:section, class: "mt_m") do
          concat(render(Application::Articles::ArticleDetailsComponent.new(@article)))
        end)

        concat(content_tag(:section, class: "mt_m") do
          concat(render(Application::Articles::PaginationComponent.new(@article)))
        end)

        concat(content_tag(:section, class: "mt_m") do
          concat(link_to(theme_path(@article.theme)) do
            render(Application::Themes::ThemeCardComponent.new(@article.theme))
          end)
        end)

        concat(share)

        if @article.topics.present?
          concat(content_tag(:section, class: "mt_l_border") do
            concat(related_topics)
          end)

          concat(content_tag(:section, class: "mt_l") do
            concat(topics_link)
          end)
        end
      end)

      if @articles.present?
        concat(content_tag(:section, class: "mt_l_border") do
          concat(top_news)
        end)
      end

      concat(content_tag(:section, class: "mt_l") do
        concat(themes_link)
      end)
    end
  end

  private

  def breadcrumbs
    render(
      Application::Common::BreadcrumbsComponent.new(
        items: [
          {
            name: "TOP",
            path: root_path
          },
          {
            name: @article.theme.title,
            path: theme_path(@article.theme_id)
          },
          {
            name: @article.title,
            path: theme_article_path(@article.theme_id, @article.id)
          }
        ]
      )
    )
  end

  def related_topics
    capture do
      concat(content_tag(:h2, "関連トピック", class: "center_title"))

      @article.topics.each_with_index do |topic, index|
        concat(content_tag(:div, class: (index.zero? ? "" : "mt_m")) do
          concat(link_to(theme_topic_path(topic.theme_id, topic.id)) do
            render(Application::Topics::TopicCardComponent.new(topic))
          end)
        end)
      end
    end
  end

  def top_news
    capture do
      concat(content_tag(:h1, "トップニュース", class: "center_title"))
      concat(render(Application::Articles::ArticleCardListComponent.new(@articles)))
    end
  end

  def themes_link
    render(
      Application::Common::SimpleButtonComponent.new(
        text: "「#{@article.theme.title}」の記事一覧へ",
        url: theme_path(@article.theme_id)
      )
    )
  end

  def topics_link
    render(
      Application::Common::SimpleButtonComponent.new(
        text: "「#{@article.theme.title}」のトピック一覧へ",
        url: theme_topics_path(@article.theme_id)
      )
    )
  end

  def share
    capture do
      concat(content_tag(:section, "", class: "mt_m"))
      concat(
        render(
          Application::Common::TwitterShareComponent.new(
            url: theme_article_url(@article.theme_id, @article.id),
            text: "#{@article.title} | #{@article.theme.title}"
          )
        )
      )
    end
  end
end

まずは管理画面

実装が面倒なので gem activeadmin を使って工数削減。
必要な model を用意して activeadmin のファイルを rails g

https://activeadmin.info/

はい、完成。

次に記事生成部分

ここはGoogleのGeminiを使うことに。
activeadmin のカスタムページを用意して 1クリックで記事を生成できるように実装。

https://gemini.google.com/

Gemini 1.5 Flash

Flashは「小説ではなくメディアの記事のような文体」という命令をガン無視して小説を書き始めた...
→ 不採用!!

Gemini 1.5 Pro

Flashと違ってProは命令を正しく理解できているようで、こちらが期待した結果を返してくれる。
→ 採用!!(しかし、生成1回あたりの費用がお高い...)

デプロイしてみる

  • Cloud Run
    • RAM: 1GB
    • CPU: 1コア
    • オートスケール: 0台 ~ 5台
  • Cloud SQL(MySQL)
    • RAM: 600MB
    • CPU: 共有コア

きっと、これで十分だろう。

メモリが少ないかも?
と思ったけど、バズらないから大丈夫と判断。

とはいえ、不安な気持ちもあるので「お手軽で強力なページキャッシュ機能」を用意して対策。

https://zenn.dev/nir_nmttg/articles/53adcf1008e9e8

「Phantom News」のできあがり

デザインなども含めて3日くらいで完成。

https://phantom.news/

実際にGeminiで記事を生成してみる

わかりやすく 「魔王と勇者が和解して平和に発展した世界」 というテーマでニュース記事を生成してみた結果がこれ。

https://phantom.news/themes/1/articles/1

とてもいい感じ。
指定してないキャラクターが勝手に生まれてるのも良い。


・勇者が推し活??

https://phantom.news/themes/1/articles/5


・ダイエットに失敗するドラゴン??

https://phantom.news/themes/1/articles/10


・勇者と魔王って意外と仲良しなのでは??

https://phantom.news/themes/1/articles/15


・仲良しかと思ったら売られてるwww

https://phantom.news/themes/1/articles/17


・推し活で狂ってしまう人も...

https://phantom.news/themes/1/articles/22


・ドラゴンの推し活ダイエット?w

https://phantom.news/themes/1/articles/24

生成するたびに世界が広がる

あれ??なんだか記事の出来事が繋がってるような?
そう、実はキャラクターの関係性や過去の出来事を考慮して、そこから新しい記事が生成される仕様にしてるんです。

やってるのはこれだけ

  1. 簡単な設定を与えて架空世界の記事を生成
  2. 1の内容と過去の記事を与えて新しい記事を生成

これだけで世界が広がって、キャラクターが増えて、歴史が積み重なる。

ガチャ気分で記事を生成

これは運営側の楽しみ。
毎回「次はどんな記事かな?」とワクワクしながら生成して、気に入った記事のみ採用して公開する。
世界に干渉できて、なんだか架空世界の神になったような気持ち。
とても楽しい。

随時更新のトピック一覧

記事が増えるごとにキャラクター紹介が更新されて、存在しない世界の存在を感じれる不思議なページ。
このページ、個人的にお気に入り。

https://phantom.news/themes/1/topics

ランニングコストはGoogleアドセンスで回収

審査に落ちました。助けてください。
記事数は結構あるはずなんだけどなぁ...

それでも運営は続ける

このままでは確実に赤字ですが、しばらくは出力結果を見守りながら毎日投稿する予定です。
「面白い!」と思ってくれた人は、ブックマークやSNSへのシェアよろしくお願いいたします!!

https://phantom.news/

https://x.com/phantom_news_tw

今のところはモチベだけが原動力です。
おしまい。

Discussion