[GPT4o,DALL-E 3,PHP] 指定画像と似たような画像を自動生成する

2025/04/11 (金) - 09:00 Program

GPT4oで画像の解析や生成がパワーアップして、かなり自然なイラストや画像が出来るようになって「この写真の人物をジブリ風にして!」みたいなことがさらに簡単にできるようになりました。今回はGPTで指定した画像を解析し、さらにその画像に近い画像をDALL-E 3で生成できるようなものをPHPで作ってみました。

猫の生成イメージ

まずchatGPTのAPIのGPT4oのモデルを使って指定した画像の分析を行い、解析結果をもとに生成プロンプトを出力させます。次にAPIのDALL-Eのモデルを使ってプロンプトを元に画像を生成します。

<?php
define('API_KEY', '{GPTのAPIキー}');
define('API_CHAT', 'https://api.openai.com/v1/chat/completions');
define('API_IMG', 'https://api.openai.com/v1/images/generations');
$base_url = '{画像のURL}';
$image_url = '';
function analyzeImage($base_url) {
 $imageData = file_get_contents($base_url);
 $base64Image = base64_encode($imageData);
 $dataUrl = 'data:image/jpeg;base64,' . $base64Image; //画像の形式によって変更
 $headers = [
  'Content-Type: application/json',
  'Authorization: Bearer ' . API_KEY,
 ];
 $data = [
  'model' => 'gpt-4o-mini',
  'messages' => [
   [
    'role' => 'user',
    'content' => [
     [
      'type' => 'text',
      'text' => 'この画像と同じ画像を生成したいので、プロンプトを生成してください'
     ],
     [
      'type' => 'image_url',
      'image_url' => ['url' => $dataUrl]
     ]
    ]
   ]
  ],
  'max_tokens' => 1000,
 ];
 $ch = curl_init(API_CHAT);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 $response = curl_exec($ch);
 $responseCode = curl_getinfo($ch,CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 if($responseCode === 200){
  $responseBody = json_decode($response, true);
  $responseMessage = $responseBody['choices'][0]['message']['content'];
  imageGenerator($responseMessage,$base_url);
 }
}
function imageGenerator($prompt,$base_url){
 $headers = [
  'Content-Type: application/json',
  'Authorization: Bearer ' . API_KEY,
 ];
 $data = [
  'model' => 'dall-e-3',
  'prompt' => $prompt,
  'n' => 1, //生成する画像の個数
  'size' => '1024x1024',
 ]
 $ch = curl_init(API_IMG);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 $response = curl_exec($ch);
 $responseCode = curl_getinfo($ch,CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 if($responseCode === 200){
  $result = json_decode($response, true);
  $image_url = $result['data'][0]['url'];
  echo '<p>元画像:<img src="'.$base_url.'" alt="" loading="lazy"></p>';
  echo '<p>生成画像:<img src="'.$image_url.'" alt="" loading="lazy"></p>';
 }
}
analyzeImage($base_url);
?>

画像の解析はURL指定だとうまく行かなかったので、base64にエンコードして指定しました。

コードは汚いけど上記のような感じです。実行すると画像の解析と画像の生成によりすこし待たされますし課金もされるので、生成する個数は少ない方が良いかもしれないです。また正常に解析や生成できなかった場合のエラー処理なども省いています。

人物の生成イメージ

結果については元画像の内容やガチャのようなところもあるので、実際はプロンプトの出力まで行ってからimageFXやStable Diffusionで納得いくまで生成を試した方が効率的かもしれません。

Next.js[App Router]&microCMSでRSSフィードを実装する

2025/03/31 (月) - 09:00 JavaScript

microCMSとNext.js[App Router]でRSSを実装してみる。RSSはブログやニュース配信サイトでは必ず実装しておくべきものです。今回は以下の環境で実装します。

  • Next.js 14.2.x
  • microcms-js-sdk 3.1.x

.envで、サイト名、サイトの説明文、サイトのドメイン、microCMSのAPIキーを設定をしておきます。

SITE_URL=https://example.com
SITE_NAME=ゆかちぃのブログ
SITE_DISCRIPTION=ゆかちぃの日々を書き残すブログです♪
API_KEY={APIキー}

src/feed/route.ts に以下のように実装。タイトル、パーマリンク、サマリー、公開日の最低限の要素のみ実装しています。

APIスキーマは以下を想定します。

  • 記事タイトル:title
  • 本文:content
  • パーマリンク:id
  • 公開日:publishedAt
import { NextResponse } from 'next/server';
import { client } from '../libs/Client.js';
import { format } from 'date-fns';
export async function GET() {
const posts = await client.get({
 endpoint: 'post',
});
 const rss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
 <channel>
  <title>${process.env.SITE_NAME}</title>
  <link>${process.env.SITE_URL}</link>
  <description>${process.env.SITE_DISCRIPTION}</description>
  ${posts.contents.map((post) => `
  <item>
   <title>${sanitizeStr(post.title)}</title>
   <link>${process.env.SITE_URL}/${post.id}</link>
   <description><![CDATA[${post.content}]]></description>
   <pubDate>${format(new Date(post.publishedAt), 'EEE, dd MMM yyyy HH:mm:ss xx')}</pubDate>
  </item>`).join('')}
 </channel>
</rss>`;
 return new NextResponse(rss, {
  headers: {
   'Content-Type': 'application/rss+xml',
  },
 });
}
function sanitizeStr(str: string): string {
 return String(str).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&apos;").replace(/"/g,"&quot;")
}

また、出力したRSSをすべてのページのhead要素内に設定し、このサイトではRSS配信しているよ!ということを認識させましょう。

<link rel="alternate" type="application/rss+xml" href="https://example.com/feed/" />

[Next.js+microCMS] 1ページ内でカテゴリごと記事一覧を表示

2025/03/21 (金) - 09:00 JavaScriptProgram

Jamstackの開発が活発になり、ヘッドレスCMSとして和製のmicroCMSを採用するケースもあるかと思います。今回はmicroCMSとNext.jsを使って記事カテゴリごとに記事一覧を分割して表示させる方法をメモします。

Next.js+microCMS 作成イメージ

記事カテゴリAPIと記事APIの2つを使います。記事のAPIスキーマにはタイトルや記事本文のほか、カテゴリ用のフィールドを用意しコンテンツ参照で記事カテゴリAPIを参照します。また開発にはmicrocms-js-sdkを使っています。今回は社員のスタッフリストをつくりました。役職ごとカテゴリを作りその下に所属するスタッフを一覧で表示します。

今回は下記の環境にて作成。

  • Node.js 20.18.x
  • Next.js 14.2.x
  • microcms-js-sdk 3.1.x

microCMSカテゴリ管理

スタッフリスト部分はコンポーネントとしてimportします。スタッフ一覧ページのファイルを/staff/page.jsとします。

import { client } from '@/libs/client';
import StaffList from '@/component/StaffList';
export default async function Home() {
 const category = await client.get({
 endpoint: 'staff_category',
 });
 return (
 <div>
 {category.contents.map((category) => (
  <article key={category.id}>
   <h2>{category.name}一覧</h2>
   <StaffList id={category.id} />
  </article>
 ))}
 </div>
 );
}

スタッフ一覧コンポーネントとして/component/StaffList.js用意します。

import Link from 'next/link';
import { client } from '@/libs/client';
import { format } from 'date-fns';
export default async function StaffList({id}){
 const category_staff = await client.get({
 endpoint: 'post',
 queries: { filters: `category[equals]${id}`}
 });
 return (
 <ul>
 {category_staff.contents.map((staff_data) => (
  <li key={staff_data.id}>
  <Link href={`/staff/${staff_data.id}`}>
  <img src={staff_data.eyecatch.url} alt="" loading="lazy" width="100" height="100" />
   <h3>{staff_data.title}</h3>
   <p><time>入社日:{format(new Date(staff_data.date), 'yyyy.MM')}</time></p>
  </Link>
  </li>
 ))}
 </ul>
 );
}

スタッフ詳細ページとして/staff/[id]/page.js用意します。

import { client } from '@/libs/client';
import { format } from 'date-fns';
export async function generateStaticParams() {
 const data = await client.get({
  endpoint: 'post',
 });
 return data.contents.map((data) => {
  return { id: data.id}
 })
}
export default async function DetailPage( props ) {
 const data = await client.get({
  endpoint: `post/${props.params.id}`,
 });
 return (
  <div>
   <p>{data.category.name}</p>
   <h1>{data.title}</h1>
   <img src={data.eyecatch.url} alt="" loading="lazy" width="100" height="100" />
   <p><time>入社日:{format(new Date(data.date), 'yyyy.MM')}</time></p>
   <div dangerouslySetInnerHTML={{ __html: data.content }}></div>
  </div>
 );
}

所属部署(カテゴリ)、名前、顔写真、入社日、自己紹介メッセージが表示されました。

詳細画面イメージ

コードの細かい内容についてはそれぞれのCMSに合わせてください。ところで、microCMSって2025年6月10日から値上がりするんですよね。むむー…

Astro&WordPressで記事一覧(ページネーション)と記事詳細ページの実装

2025/03/14 (金) - 09:00 JavaScriptProgram

以前に副業でWordPressとAstroでヘッドレスCMSでブログを実装しましたのでそのメモです。取り急ぎ記事一覧と記事詳細のみでファイル構成は最低限のものだけ。

src
├─ components
│    └─ Pagination.astro
├─ layouts
│    └─ Layout.astro
└─ pages
     ├─ [slug].astro
     ├─ index.astro
     └─ page
         └─ [paged].astro

WordPressの記事の呼び出しは、WordPressのREST APIを使います。

WordPress連携実装例

REST APIのエンドポイントは.envに記載しておきます。ついでにブログ名も設定しておくと一括で変更できます。

BLOG_TITLE=ひなちゃんの日記
API_JSON=https://example.com/wp-json/wp/v2/posts/

レイアウトを定義するにLayout.astroに大枠のHTMLとブラウザに表示されるタイトルのtitle要素の中身を取得するコードを記載します。必要最低限の記載のみです。

---
const {meta} = Astro.props;
---
<!doctype html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title>{meta.title}</title>
 </head>
 <body>
  <slot />
 </body>
</html>

トップページの記事一覧をindex.astroに実装します。ブラウザに表示されるタイトルは[トップページ – ブログ名]となります。

---
import { format } from 'date-fns';
import Layout from '../layouts/Layout.astro';
import Pagination from '../components/Pagination.astro';
const res = await fetch(`${import.meta.env.API_JSON}`);
const posts = await res.json();
const total = await fetch(import.meta.env.API_JSON).then((res) => {return res.headers.get('X-WP-TotalPages')});
const page: {
 current: number,
 total: number 
} = {
 current: 1,
 total: Number(total)
}
const meta: {
 title: string
} = {
 title: `トップページ - ${import.meta.env.BLOG_TITLE}`
}
---
<Layout meta={meta}>
 <main>
 {posts.map((post: { slug: string; title: { rendered: string }; content: { rendered: string }; date: string }) => (
 <article><a href={`/${post.slug}`}>
  <h2>{post.title.rendered}</h2>
  <p>{format(new Date(post.date), 'yyyy年MM月dd日')}</p>
 </a></article>
 ))}
 <Pagination page={page} />
</main>
</Layout>

ページネーションの記事一覧は/page/[paged].astroで動的パスで取得し、getStaticPathsで生成します。ブラウザに表示されるタイトルは[nページ目 – ブログ名]となります。

---
import { format } from 'date-fns';
import Layout from '../../layouts/Layout.astro';
import Pagination from '../../components/Pagination.astro';
export async function getStaticPaths(){
 const total = await fetch(import.meta.env.API_JSON).
 then((res) => {return Number(res.headers.get('X-WP-TotalPages'))});
 return Array.from({ length: total }, (num,i) => ({
  params: { paged: `${i + 1}` }
 }))
}
const {paged} = Astro.params;
const res = await fetch(`${import.meta.env.API_JSON}?page=${paged}`);
const posts = await res.json();
const total = await fetch(import.meta.env.API_JSON).then((res) => {return res.headers.get('X-WP-TotalPages')});
const page: {
 current: number,
 total: number 
} = {
 current: Number(paged),
 total: Number(total)
}
const meta: {
 title: string
} = {
 title: `${paged}ページ目 - ${import.meta.env.BLOG_TITLE}`
}
---
<Layout meta={meta}>
 <main>
 {posts.map((post: { slug: string; title: { rendered: string }; content: { rendered: string }; date: string }) => (
  <article><a href={`/${post.slug}`}>
   <h2>{post.title.rendered}</h2>
   <p>{format(new Date(post.date), 'yyyy年MM月dd日')}</p>
  </a></article>
 ))}
 <Pagination page={page} />
</main>
</Layout>

ページネーション部分は別コンポーネントで管理しました。components/Pagination.astroで実装し呼び出します。

---
const { page } = Astro.props;
const prev = page.current-1;
const next = page.current+1;
---
<p>{page.current} / {page.total}</p>
<p>
{page.current!=1 && (
 <a href={`/page/${String(prev)}`}>前のページへ</a>
)}
{page.current!=page.total && (
 <a href={`/page/${String(next)}`}>次のページへ</a>
)}
</p>

記事詳細は[slug].astroで動的パスで取得し、getStaticPathsで生成します。ブラウザに表示されるタイトルは[記事タイトル – ブログ名]となります。

---
import { format } from 'date-fns';
import Layout from '../layouts/Layout.astro';
export async function getStaticPaths(){
 const res = await fetch(import.meta.env.API_JSON);
 const posts = await res.json();
 const paths = posts.map((post: { slug: string }) => ({
  params: { slug: post.slug }
 }));
 return paths;
}
const {slug} = Astro.params;
const post_res = await fetch(`${import.meta.env.API_JSON}?slug=${slug}`);
const [post] = await post_res.json();
const meta: {
 title: string
} = {
 title: `${post.title.rendered} - ${import.meta.env.BLOG_TITLE}`
}
---
<Layout meta={meta}>
 <main>
  <h1>{post.title.rendered}</h1>
  <p>{format(new Date(post.date), 'yyyy年MM月dd日')}</p>
  <div set:html={post.content.rendered} />
 </main>
</Layout>

実装し終わったらプレビューしてテストします。

$ npm run dev

CSSでのデザイン実装や、metaタグ、フロント周りのマークアップで整えれば出来上がり。

関連記事

OpenAI API(DALL·E 3)とNext.js(React)で画像生成プログラム

2025/03/10 (月) - 09:00 JavaScript

Next.js(AppRouter)とOpenAI APIを利用して、テキストフォームから入力した画像を生成するプログラムを作成してみました。今回はDALL·E 3のモデルを利用しています。予めOpenAI PlatformでAPIキーの発行とクレジットを確保しておきましょう。取得したAPIキーは.envで設定します。

API_KEY=取得したAPIキー

生成された画像例

また今回は勉強がてらAPI RoutesとAxiosを使って実装を試みました。実装は以下の環境にて。

  • Next.js 14.2.x (AppRouter)
  • React 18.3.x
  • Axios 1.7.x

まず、page.jsに以下のようにフロントエンドを実装。プロンプトのテキストボックスと、作風を選ぶラジオボタンを配置しました。ここのHTMLやCSSは適当に。

'use client';
import { useState } from 'react';
import axios from 'axios';
export default function Home() {
 const [prompt, setPrompt] = useState('');
 const [type, setType] = useState('写真風');
 const [imageUrl, setImageUrl] = useState('');
 const [message, setMessage] = useState('');
 const [loading, setLoading] = useState(false);
 const generateImage = async () => {
  setLoading(true); //生成ボタンを非活性
  setImageUrl('');
  setMessage('');
  try {
   const requestPrompt = `${prompt} ${type}で生成してください`;
   const response = await axios.post('/api/imagegenerate', { prompt : requestPrompt });
   setImageUrl(response.data.imageUrl);
   setMessage('画像ができました!');
  } catch (error) {
   setMessage(`画像が生成できませんでした:${error}`);
  }
  setLoading(false); //生成ボタンを活性
 };
 return (
  <div>
   <label htmlFor="type1"><input type="radio" name="type" id="type1" onChange={(e) => setType(e.target.value)} value="写真風" defaultChecked="true" />写真風</label>
   <label htmlFor="type2"><input type="radio" name="type" id="type2" onChange={(e) => setType(e.target.value)} value="イラスト風" />イラスト風</label>
   <label htmlFor="type3"><input type="radio" name="type" id="type3" onChange={(e) => setType(e.target.value)} value="水彩画風" />水彩画風</label>
   <hr />
   <textarea name="prompt" id="prompt" onChange={(e) => setPrompt(e.target.value)} placeholder="プロンプトを入力" defaultValue={prompt}></textarea>
   <hr />
   <button onClick={generateImage} disabled={loading}>{loading ? '生成中です' : '画像を生成する'}</button>
   <p>{message}</p>
   {imageUrl && (
    <div>
     <h2>生成された画像</h2>
     <img src={imageUrl} alt="生成された画像です" width="1024" height="1024" />
    </div>
   )}
  </div>
 );
}

API面をフロントエンドからのリクエストとDALL·E APIの中継を実装します。ソースディレクトリに/api/imagegenerate/route.jsを配置し、以下のように実装しました。page.jsからPOSTでパラメータでリクエストされたプログラムを処理します。

import { NextResponse } from 'next/server';
import axios from 'axios';
export async function POST(req) {
 try {
  const { prompt } = await req.json();
  if (!prompt) {
   return NextResponse.json({ error: 'プロンプトを入力してください' }, { status: 400 });
  }
  const response = await axios.post(
   'https://api.openai.com/v1/images/generations',
   {
    prompt,
    n: 1,
    size: '1024x1024', //サイズ
    model: 'dall-e-3'
   },
   {
    headers: {
     Authorization: `Bearer ${process.env.API_KEY}`,
     'Content-Type': 'application/json',
    },
   }
  );
  return NextResponse.json({ imageUrl: response.data.data[0].url }, { status: 200 });
 } catch (error) {
  console.error('画像生成エラー:', error.response?.data || error.message);
  return NextResponse.json({ error: '画像生成に失敗しました' }, { status: 500 });
 }
}

イラスト風を選択して生成した場合の表示例。

生成された画像例

プロンプトを色々アレンジすれば面白い画像ジェネレータが作れそうです。

CSSでスクロールバーの色を変更する

2025/03/04 (火) - 09:00 CSS

CSSでスクロールバーの色を変えたり、可視・不可視を変更することが出来ます。デザインの幅が広がりますがアクセシビリティを損なわないように注意しましょう。

html{
 scrollbar-color: #fff #e587cb; /* つまみの色 背景色 */
 scrollbar-width: auto;
}

scrollbar-colorプロパティでスクロールバーのつまみの色と背景色を指定します。scrollbar-widthで幅を指定できます。こちらは以下の値を指定します。

  • auto:デフォルト幅
  • thin:スクロールバーを細くする
  • none:スクロールバーを非表示

スクロールバーの色を変更した例

Safari(Webkit)で指定する場合

古いSafariではまだ未対応なので独自実装のCSSセレクタで指定します。こちらのほうが細かくデザインを指定できます。

html::-webkit-scrollbar {
 width: 15px; /* スクロールバーの横幅 */
}
html::-webkit-scrollbar-track-piece {
 background-color: #e587cb; /* Safari スクロールバーの背景色 */
}
html::-webkit-scrollbar-thumb {
 background-color: #fff; /* Safari スクロールバーのつまみの色 */
 border: 3px solid #e587cb; /* Safari スクロールバーのつまみの枠色 */
 border-radius: 10px;
 background-clip: content-box;
}

スクロールバーを非表示にする場合はdisplay: none;を指定します。

html::-webkit-scrollbar {
 display: none;
}

おまけ

かつて覇権を握っていた憎きIE(Internet Explorer)でも、CSS独自実装でスクロールバーの色を変えたり出来ました。一部のバージョンのWindows Operaでも再現します。

IEでスクロールバーの色を変更した例

IE5とかでサポートされており、他のサイトとデザインの差別化が出来たとか出来ないとか。

WordPress 記事のタグで絞り込み検索を実装

2025/02/28 (金) - 09:00 Program

WordPressの記事タグで絞り込み検索をする機能を実装。例えばドラクエの技で「メラ」&「ブレス」、「ヒャド」&「呪文」の条件にあった技だけを検索したいときなどに使える方法。

検索結果

実際に実装した表示例。「デイン」&「斬撃」で検索した結果を表示。実装は以下の環境にて。

  • WordPress 6.5.x
  • PHP 8.2.x

まず、任意の場所に検索エリアを実装。タグを選ぶチェックボックスとテキストエリアを実装します。

<form role="search" method="get" id="search" action="<?php echo esc_url( home_url( '/' ) ); ?>">
 <input type="text" value="<?php echo get_search_query(); ?>" name="s" id="s" placeholder="キーワードを入力" />
<?php
 $tags = get_tags();
 if ( $tags ) {
  foreach ( $tags as $tag ) {
?>
<label for="tag-<?php echo esc_attr( $tag->slug ); ?>"><input type="checkbox" name="tag[]" value="<?php echo esc_attr( $tag->slug ); ?>" id="tag-<?php echo esc_attr( $tag->slug ); ?>"><?php echo esc_html( $tag->name ); ?></label>
<?php
  }
 }
?>
<input type="submit" id="buttonsubmit" name="buttonsubmit" value="検索する" />
</form>

検索結果画面の実装。search.phpの任意の位置に以下を実装します。必要な要素などはWebサイトの内容に合わせて各自で設定ください。

<?php
 $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
 $search_query = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : '';
 $search_tags = isset( $_GET['tag'] ) && is_array( $_GET['tag'] ) ? array_map( 'sanitize_text_field', $_GET['tag'] ) : [];
 $args = array(
  's' => $search_query,
  'tag_slug__and' => $search_tags,
  'paged' => $paged,
 );
 $query = new WP_Query( $args );
 if ( $query->have_posts() ) {
?>
<ul>
<?php
  while ( $query->have_posts() ) {
   $query->the_post();
?>
 <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
  }
?>
</ul>
<?php
  the_posts_pagination( array(
   'total' => $query->max_num_pages,
  ));
  wp_reset_postdata();
 }else{
?>
<p>残念ながら、技は見つかりませんでした。</p>
<?php
 }
?>

あくまで例ですが、店舗情報でサービス内容(駐車場、キャシュレス決済)で絞り込みや、アパレル商品のスペック(サイズやカラー)検索でも活用できます。

Astro&microCMSで1ページ内にカテゴリごとの記事一覧を表示する

2025/02/25 (火) - 09:00 JavaScriptProgram

AstroとmicroCMSで1ページ内にカテゴリ事の見出しを表示し、さらにそのカテゴリの記事一覧を表示したい場合の実装方法。例えば以下のような表示イメージです。

カテゴリ別記事一覧イメージ

なお今回は下記の環境で実装しました。

  • Astro 4.16.x
  • microcms-js-sdk 3.1.x

まず運用面で予めmicroCMS側にカテゴリ用のコンテンツAPIと、その配下の記事用コンテンツAPIを用意しておきます。具体的には記事APIでコンテンツ参照機能をを使用してカテゴリAPIを参照するようにしておきます。今回は例として、カテゴリをitem_categoryというエンドポイントで用意し記事をitemというエンドポイントで作成しております。詳しくは公式ドキュメントのとおり

設定ファイルを用意する

簡単なsrcディレクトリのファイル構成です。

├─ components
│   └── List.astro
├─ layouts
│   └── Layout.astro
├─ library
│   └── microcms.ts
└─ pages
    └── index.astro

.envファイルに予めmicroCMSのサービスのID(example.microcms.ioのexampleの部分)とAPIキーをします。

MICROCMS_SERVICE_DOMAIN=【サービスのID】
MICROCMS_API_KEY=【APIキー】

library/microcms.tsにmicroCMSの設定ファイルを記述します。ここではサービスのIDとAPIキーなどを指定します。

import type { MicroCMSQueries } from 'microcms-js-sdk';
import { createClient } from 'microcms-js-sdk';
const client = createClient({
 serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
 apiKey: import.meta.env.MICROCMS_API_KEY
});

一覧ページを実装する

pages/index.astroにカテゴリの見出しと記事一覧を実装します。わかりやすいように記事部分は別のコンポーネントにして、記事IDをパラメータで渡すようにおきました。

---
import {client} from '../library/microcms';
import Layout from '../layouts/Layout.astro';
import List from '../components/List.astro';
const category = await client.get({
 endpoint: 'item_category' //カテゴリAPIのエンドポイント
});
---
<Layout>
 {category.contents.map((category:any) => (
 <article>
  <h2>{category.name}</h2>
  <List id={category.id} />
 </article>
 ))}
</Layout>

components/List.astroに記事部分を実装します。テストのため記事タイトルとアイキャッチを指定していますが、こちらはそれぞれの記事に応じて変更ください。

---
import {client} from '../library/microcms';
const {id} = Astro.props;
const items = await client.get({
 endpoint: 'item', //記事APIのエンドポイント
 queries: { filters: `category[equals]${id}`} //カテゴリ用スキーマ名を指定
});
---
<ul>
{items.contents.map((item_data:any) => (
 <li>
  <a href={`/article/${item_data.id}`}>
   <img src={item_data.eyecatch.url} alt="" loading="lazy" width="100" height="100" />
   <p>{item_data.title}</p>
  </a>
 </li>
))}
</ul>

今回は一覧のみで、記事詳細ページの実装などは省いています。これで簡単なカテゴリ記事リストを実装できます。

PHPでDeepSeek APIを使ってみた。

2025/02/18 (火) - 09:00 Program

何かと話題だったDeepSeekDeepSeek APIがずっとメンテナンスで使えなかったのですが、気がついたら動くようになっていたので軽く触ってみました。

APIを使う場合は予めDeepSeek API PlatformからAPIキーの取得を済ませておく必要があります。発行時にAPIキーは一度しか表示されないので忘れずにメモしましょう。今はAPIのプロモーションが終っているので、使う場合は課金しておく必要があります(追記 2025年2月現在は、課金が停止されているので、課金したくとも出来ない模様)。

ドラクエ3のゾーマの弱点と倒し方を教えて

DeepSeekの安全性や信頼性の是非はあるものの、料金体系がGPTやClaudeより安いので個人利用で試しに使うのはアリかなと。

コード例

入力フォームから質問したら次の画面に遷移して回答を表示する流れです。以下はHTMLコード。

<form method="POST" action="">
 <label for="prompt">質問を入れてね:
 <textarea name="prompt" id="prompt" placeholder="質問を入力"><?php echo $prompt; ?></textarea></label>
 <input type="submit" value="質問する">
</form>

以下は対応するPHPのコード(エラーやセキュリティ対策は省いてます)。ほぼGPTと一緒ですね…なぜなら公式のドキュメントですらOpen AIのSDKが使えるよ、と言ってるくらいなので…これって…。

<?php
$API_KEY = '{取得したAPIキーを入れる}';
$API_URL = 'https://api.deepseek.com/chat/completions';
$prompt = htmlspecialchars($_POST['prompt'], ENT_QUOTES, 'UTF-8');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 $data = array(
  'model' => 'deepseek-chat', //モデルを選択
  'system' => [
   ['role' => 'system', 'content' => '語尾ににゃんを付けてください']
  ],  
  'messages' => [
   ['role' => 'user', 'content' => $prompt]
  ],
  'max_tokens' => 1000
 );
 $headers = array(
  'Content-Type: application/json',
  'Authorization: Bearer ' . $API_KEY
 );
 $ch = curl_init($API_URL);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 $response = curl_exec($ch);
 $httpcode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 if($httpcode===200){
  $result = json_decode($response, true);
  $answer = $result['choices'][0]['message']['content'];
  echo nl2br($answer);
 } else {
  echo 'エラーが発生したにゃん:'.$response;
 }
}
?>

また、利用モデルや料金体系などは以下のドキュメント参照のこと。

ねこですよろしくおねがいします

あなたは犬と猫どちらが好きですか?

にゃん、わたしは猫の方が好きにゃ!でも犬もかわいいにゃ~。あなたはどっちが好きにゃ?

犬派か猫派かでは猫派だそうです。

PHPでClaude APIを使ってみた。

2025/02/14 (金) - 09:00 Program

大規模言語モデルの対話型生成AI、Claude APIをPHPと連携して使ってみたので、そのメモ。

ClaudeのAPIを使う場合は、Anthropic Consoleに登録・ログイン後に[Get API keys]で予めAPIキーを取得する必要があり、それとは別にクレジットカードでも課金しておく必要もあります。

Claude API連携イメージ

今回は単純にフォームのテキストエリアから送信した文に答えるだけの簡単なテストを実装しました。HTMLは以下の通り。

<form method="post" action="">
 <textarea name="prompt" id="prompt" placeholder="メッセージを入れてにゃん"><?php echo $prompt ?></textarea>
 <input type="submit" value="送信">
</form>

PHP部分は以下の通り。POSTで受け取ったデータをClaude APIにプロンプトをリクエストし、レスポンスを画面上に表示するのみです。例として回答文は語尾に「にゃん」を付けるような条件としました。

<?php	
 define(API,'https://api.anthropic.com/v1/messages');
 define(API_KEY,'{API キーを指定}');
 $system = '語尾ににゃんをつけて可愛く回答してください。';
 $prompt = htmlspecialchars($_POST['prompt'], ENT_QUOTES, 'UTF-8');
 if($prompt && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $data = json_encode([
   'model' => 'claude-3-5-haiku-20241022', //モデルを指定
   'max_tokens' => 1024,
   'system' => $system,
   'messages' => [
    ['role' => 'user', 'content' => $prompt]
   ]
  ]);
  $ch = curl_init(API);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
  curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
   'Content-Type: application/json',
   'anthropic-version: 2023-06-01',
   'x-api-key: ' . API_KEY
  ]);		
  $result = curl_exec($ch);
  curl_close($ch);
  $output = json_decode($result, true);
  if($output['type']==='error'){
   echo 'エラーですにゃん';
  }else{
   echo nl2br($output['content'][0]['text']);
  }
 }else{
  echo 'メッセージを入れてにゃん';
 }
?>

こう見るとGPT APIと似たような設計ですね。Claudeを開発した「Anthropic(アンソロピック)」は、ChatGPTを開発したOpenAI社に元メンバーが作った企業なので、その名残なのかもしれません。今回モデルはclaude-3-5-haiku-20241022を指定しておりますが、人にのモデルに変更が可能です。詳しいことは以下のドキュメントに案内があります。

プロンプトですが、例えば以下のような文面をリクエストした場合

碇ゲンドウとゼーレが進める人類補完計画とはなんですか?
碇ゲンドウとゼーレの目的は、人類補完計画を成功させることにゃん!

碇ゲンドウは、全ての人間の意識を一つに融合させて、個人の痛みや孤独から解放し、新たな進化の形を目指すにゃん。ゲンドウは息子・碇シンジを利用して、

〜略〜

最終的には、この計画が人類の存在そのものを根本的に変えようとする壮大な試みだったにゃん!

上記のようなレスポンスが返ってくるのでフロントでは自動的にHTMLで改行するようnl2brを実行しました。

ページの先頭へ