🤖

Windsurf Editorの魅力

2024/12/05に公開

次世代エディターの実力

最近存在を知った生成AIと連携したVScodeと同じ感覚で使用することができるWindsurf Editorがどれほどのものなのかを試してみた。

https://codeium.com/

前回は、GoとFlutterで試してみたのだが予想以上の精度の高いコードを書いてくれた。ディレクトリやファイルを自動生成してくれるのでプロトタイプを作るのには良いかもしれない。

自分でコード書かなくも良いとは。。。。
書こうと思ったら書いてくれてる笑

Next.jsでUI作成してみる

こちらに作ってみたUIのサンプルがございます。まさかプロンプトを入力するだけで作ってくれるとは。。。。
すごすぎる!

デモ用のリポジトリ

npx create-next-app@latest

管理画面を作る

これ全部AIが作りました(^_^;)
精度高すぎる?
思った通りのレイアウトは作ってくれないんですよね。

'use client';

import { useState } from 'react';

interface Member {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
  joinDate: string;
  lastLogin: string;
}

export default function AdminPage() {
  // サンプルデータ
  const [members] = useState<Member[]>([
    {
      id: 1,
      name: "山田 太郎",
      email: "yamada@example.com",
      status: "active",
      joinDate: "2024-01-15",
      lastLogin: "2024-03-20"
    },
    {
      id: 2,
      name: "佐藤 花子",
      email: "sato@example.com",
      status: "active",
      joinDate: "2024-02-01",
      lastLogin: "2024-03-19"
    },
    {
      id: 3,
      name: "鈴木 一郎",
      email: "suzuki@example.com",
      status: "inactive",
      joinDate: "2024-01-20",
      lastLogin: "2024-02-28"
    },
  ]);

  return (
    <div className="min-h-screen bg-gray-50 flex flex-col">
      {/* Header */}
      <header className="bg-white shadow-sm h-16">
        <div className="max-w-7xl mx-auto px-4 h-full flex items-center justify-between">
          <div className="flex items-center gap-4">
            <div className="w-10 h-10 rounded-full bg-blue-500 flex-shrink-0" />
            <h1 className="text-xl font-bold">サロン管理画面</h1>
          </div>
          <div className="flex items-center gap-4">
            <span className="text-gray-600">管理者:Admin</span>
          </div>
        </div>
      </header>

      <div className="flex-1 flex">
        {/* Sidebar */}
        <aside className="w-64 bg-white shadow-sm">
          <nav className="p-4 space-y-2">
            <div className="px-3 py-2 text-sm font-medium text-blue-600 bg-blue-50 rounded-md">
              メンバー管理
            </div>
            <div className="px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 rounded-md">
              売上管理
            </div>
            <div className="px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 rounded-md">
              コンテンツ管理
            </div>
            <div className="px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 rounded-md">
              設定
            </div>
          </nav>
        </aside>

        {/* Main Content */}
        <main className="flex-1 p-8">
          <div className="bg-white rounded-lg shadow-sm">
            <div className="p-6 border-b border-gray-200">
              <div className="flex justify-between items-center">
                <h2 className="text-lg font-semibold">メンバー一覧</h2>
                <button className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
                  新規メンバー追加
                </button>
              </div>
            </div>
            <div className="overflow-x-auto">
              <table className="w-full">
                <thead className="bg-gray-50">
                  <tr>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      名前
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      メールアドレス
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      ステータス
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      登録日
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      最終ログイン
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      アクション
                    </th>
                  </tr>
                </thead>
                <tbody className="bg-white divide-y divide-gray-200">
                  {members.map((member) => (
                    <tr key={member.id}>
                      <td className="px-6 py-4 whitespace-nowrap">
                        <div className="text-sm font-medium text-gray-900">{member.name}</div>
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap">
                        <div className="text-sm text-gray-500">{member.email}</div>
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap">
                        <span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
                          member.status === 'active' 
                            ? 'bg-green-100 text-green-800' 
                            : 'bg-red-100 text-red-800'
                        }`}>
                          {member.status === 'active' ? '有効' : '無効'}
                        </span>
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                        {member.joinDate}
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                        {member.lastLogin}
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
                        <button className="text-blue-600 hover:text-blue-900 mr-3">
                          編集
                        </button>
                        <button className="text-red-600 hover:text-red-900">
                          削除
                        </button>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </main>
      </div>

      {/* Footer */}
      <footer className="bg-white shadow-sm mt-auto">
        <div className="max-w-7xl mx-auto px-4 py-4">
          <div className="text-center text-gray-500 text-sm">
            <p>&copy; 2024 Online Salon Admin. All rights reserved.</p>
          </div>
        </div>
      </footer>
    </div>
  );
}

ブログページ

UIだけですが、ブログページを作ってみました。

'use client';

import { useState } from 'react';

export default function BlogPage() {
  const [isDrawerOpen, setIsDrawerOpen] = useState(false);

  return (
    <div className="min-h-screen bg-gray-50">
      {/* Header */}
      <header className="bg-white shadow-sm">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
          <div className="flex items-center gap-4">
            <div className="w-10 h-10 rounded-full bg-blue-500 flex-shrink-0" />
            <h1 className="text-xl font-bold">My Blog</h1>
          </div>
          
          {/* Mobile menu button */}
          <button
            onClick={() => setIsDrawerOpen(!isDrawerOpen)}
            className="lg:hidden p-2 rounded-md hover:bg-gray-100"
          >
            <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
            </svg>
          </button>
        </div>
      </header>

      {/* Mobile Drawer */}
      {isDrawerOpen && (
        <div className="fixed inset-0 z-40 lg:hidden">
          <div className="fixed inset-0 bg-black bg-opacity-50" onClick={() => setIsDrawerOpen(false)} />
          <div className="fixed inset-y-0 right-0 max-w-xs w-full bg-white shadow-xl">
            <div className="p-6">
              <div className="flex items-center justify-between mb-6">
                <h2 className="text-lg font-medium">Menu</h2>
                <button onClick={() => setIsDrawerOpen(false)} className="p-2">
                  <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                  </svg>
                </button>
              </div>
              <nav className="space-y-4">
                <a href="#" className="block hover:text-blue-500">Home</a>
                <a href="#" className="block hover:text-blue-500">About</a>
                <a href="#" className="block hover:text-blue-500">Contact</a>
              </nav>
            </div>
          </div>
        </div>
      )}

      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
        <div className="lg:grid lg:grid-cols-12 lg:gap-8">
          {/* Main Content */}
          <main className="lg:col-span-8">
            <article className="bg-white rounded-lg shadow-sm p-6 mb-8">
              <h2 className="text-2xl font-bold mb-4">公園でのお散歩日記</h2>
              <div className="prose max-w-none">
                <p className="text-gray-600 mb-4">
                  今日は天気が良かったので、近所の公園へお散歩に行ってきました。
                  桜の木々が美しく咲き誇り、春の訪れを感じる素敵な一日でした。
                </p>
                <p className="text-gray-600 mb-4">
                  公園では、たくさんの家族連れや犬の散歩をする人々を見かけました。
                  子供たちは元気に遊具で遊び、その笑顔が印象的でした。
                </p>
                <p className="text-gray-600">
                  季節の変わり目を感じながらの散歩は、心が癒される素敵な時間となりました。
                  また明日も、違う公園に行ってみようと思います。
                </p>
              </div>
            </article>
          </main>

          {/* Sidebar */}
          <aside className="lg:col-span-4">
            <div className="bg-white rounded-lg shadow-sm p-6 sticky top-8">
              <h3 className="text-lg font-semibold mb-4">最近の投稿</h3>
              <ul className="space-y-4">
                <li>
                  <a href="#" className="text-blue-500 hover:underline">公園でのお散歩日記</a>
                </li>
                <li>
                  <a href="#" className="text-blue-500 hover:underline">春の花々を見に行った日</a>
                </li>
                <li>
                  <a href="#" className="text-blue-500 hover:underline">新しいカメラで撮影した写真</a>
                </li>
              </ul>
            </div>
          </aside>
        </div>
      </div>

      {/* Footer */}
      <footer className="bg-white mt-12">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
          <div className="text-center text-gray-500">
            <p>&copy; 2024 My Blog. All rights reserved.</p>
          </div>
        </div>
      </footer>
    </div>
  );
}

アナログ時計を作る

tailwindcssとreact hooksだけで作れる。

'use client';

import { useEffect, useState } from 'react';

export default function ClockPage() {
  const [mounted, setMounted] = useState(false);
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    setMounted(true);
    const timer = setInterval(() => {
      setTime(new Date());
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  const hours = time.getHours();
  const minutes = time.getMinutes();
  const seconds = time.getSeconds();

  // 時針、分針、秒針の角度を計算
  const hourDegrees = (hours % 12 + minutes / 60) * 30; // 360度 ÷ 12 = 30度
  const minuteDegrees = (minutes + seconds / 60) * 6; // 360度 ÷ 60 = 6度
  const secondDegrees = seconds * 6; // 360度 ÷ 60 = 6度

  // マウント前は何も表示しない
  if (!mounted) {
    return null;
  }

  return (
    <div className="min-h-screen bg-gray-50 flex items-center justify-center">
      <div className="relative w-80 h-80 rounded-full bg-white shadow-xl border-8 border-gray-200">
        {/* 時間のマーカー */}
        {[...Array(12)].map((_, i) => (
          <div
            key={i}
            className="absolute w-1 h-6 bg-gray-800"
            style={{
              transform: `rotate(${i * 30}deg)`,
              transformOrigin: '50% 150px',
              left: 'calc(50% - 2px)',
            }}
          />
        ))}

        {/* 分のマーカー */}
        {[...Array(60)].map((_, i) => {
          if (i % 5 !== 0) { // 時間のマーカーと重複しないように
            return (
              <div
                key={i}
                className="absolute w-0.5 h-3 bg-gray-400"
                style={{
                  transform: `rotate(${i * 6}deg)`,
                  transformOrigin: '50% 150px',
                  left: 'calc(50% - 1px)',
                }}
              />
            );
          }
        })}

        {/* 時針 */}
        <div
          className="absolute w-2 h-24 bg-black rounded-full origin-bottom"
          style={{
            bottom: '50%',
            left: 'calc(50% - 4px)',
            transform: `rotate(${hourDegrees}deg)`,
            transformOrigin: '50% 100%',
          }}
        />

        {/* 分針 */}
        <div
          className="absolute w-1.5 h-32 bg-black rounded-full origin-bottom"
          style={{
            bottom: '50%',
            left: 'calc(50% - 3px)',
            transform: `rotate(${minuteDegrees}deg)`,
            transformOrigin: '50% 100%',
          }}
        />

        {/* 秒針 */}
        <div
          className="absolute w-1 h-36 bg-red-500 rounded-full origin-bottom"
          style={{
            bottom: '50%',
            left: 'calc(50% - 2px)',
            transform: `rotate(${secondDegrees}deg)`,
            transformOrigin: '50% 100%',
          }}
        />

        {/* 中心の円 */}
        <div className="absolute w-4 h-4 bg-red-500 rounded-full"
          style={{
            top: 'calc(50% - 8px)',
            left: 'calc(50% - 8px)',
          }}
        />

        {/* デジタル時計表示(オプション) */}
        <div className="absolute w-full text-center text-gray-600 font-mono text-sm"
          style={{
            bottom: '25%',
          }}
        >
          {time.toLocaleTimeString('ja-JP', { hour12: false })}
        </div>
      </div>
    </div>
  );
}

https://www.youtube.com/watch?v=l24t0SUiTf4

感想

精度はすごく高い。しかし気がついたら自動でファイルが生成されていたりコードを書き換えているので仕事で使うときは注意が必要かも(^^;;

Githubにpushするとコンフリクトしたりして?

Discussion