見出し画像

ClaudeのArtifacts機能でチャットアプリを作ろう!

割引あり

こんにちは!DiningXの吉波です!
ありがたいことにXのアカウントをフォローしてくださる方も増えてきて、とても嬉しい限りです!

今回は、ClaudeのArtifacts機能のチャットUIやSlack UIを作る方法を紹介します。実際、SlackのチャットUIに関する投稿は、9時間で12万インプレッションという脅威の数字を叩き出しました。

はじまり

まずはいつも通り、作りたいアプリをClaudeにお願いしましょう!

Claudeにお願いする

実際のアプリ画像を見せる

Claudeとの最初の会話で、大まかなUIの知識は教えてくれますが、詳細な部分は出力してくれないことが多いです。そこで、実際のUI画像を渡すことで、再現しやすくしてもらいます

実際の画像を見せている様子

UIの再現を依頼する

先ほど渡した画像をもとに再現をお願いします

再現をお願いする

他のチャット履歴も紹介

エラーの対処

プレビューできない場合の対処

最終的に生成されたコード

import React, { useState, useEffect } from 'react';
import { Search, ChevronDown, Hash, Lock, Home, MessageCircle, Bell, ArrowLeft, UserPlus, Link } from 'lucide-react';

const AnthropicSlackUI = () => {
  const [currentView, setCurrentView] = useState('home');
  const [selectedChannel, setSelectedChannel] = useState(null);
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    if (currentView === 'channel' && selectedChannel) {
      setMessages([
        { id: 1, user: 'Kintaro Endo', time: '14:47', content: '#team-winterに参加しました' },
        { id: 2, user: 'Saburo Nakamura', time: '15:46', content: '#team-winterに参加しました' },
        { id: 3, user: 'Hanako Yoshida', time: '15:48', content: '#team-winterに参加しました' },
        { id: 4, user: 'Kintaro Endo', time: '16:13', content: '「冬のキャンペーン」から「team-winter」にチャンネル名を変更しました' },
        { id: 5, user: 'Hanako Yoshida', time: '16:15', content: '来週の打ち合わせの資料よろしく', reactions: [{ emoji: '👍', count: 1 }] }
      ]);

      const timer = setTimeout(() => {
        setMessages(prevMessages => [
          ...prevMessages,
          { id: 6, user: 'Anthropic Bot', time: '今', content: 'We are Hiring! 🎉' }
        ]);
      }, 5000);

      return () => clearTimeout(timer);
    }
  }, [currentView, selectedChannel]);

  const handleChannelSelect = (channel) => {
    setSelectedChannel(channel);
    setCurrentView('channel');
  };

  const HomeView = () => (
    <>
      <div className="flex p-4 space-x-4 border-b border-gray-200">
        <div className="flex-1 bg-gray-100 rounded-md p-2 text-center">
          <div className="font-bold">Threads</div>
          <div className="text-sm text-purple-600">1 new</div>
        </div>
        <div className="flex-1 bg-gray-100 rounded-md p-2 text-center">
          <div className="font-bold">Later</div>
          <div className="text-sm text-red-500">1 overdue</div>
        </div>
        <div className="flex-1 bg-gray-100 rounded-md p-2 text-center">
          <div className="font-bold">Drafts & Sent</div>
          <div className="text-sm">3 scheduled</div>
        </div>
      </div>

      <div className="p-4">
        <div className="flex items-center justify-between mb-2">
          <h2 className="font-bold">Mentions</h2>
          <ChevronDown size={20} />
        </div>
        <ul>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('design-team')}>
            <Hash size={20} className="mr-2" />
            <span>design-team</span>
            <span className="ml-auto bg-purple-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs">2</span>
          </li>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('The Home Team')}>
            <div className="w-5 h-5 bg-green-500 rounded-full mr-2"></div>
            <span>The Home Team</span>
          </li>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('team-finance')}>
            <Lock size={20} className="mr-2" />
            <span>team-finance</span>
          </li>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('project-brand-campaign')}>
            <Hash size={20} className="mr-2" />
            <span>project-brand-campaign</span>
          </li>
        </ul>
      </div>

      <div className="p-4">
        <div className="flex items-center justify-between mb-2">
          <h2 className="font-bold">Channels</h2>
          <ChevronDown size={20} />
        </div>
        <ul>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('general')}>
            <Hash size={20} className="mr-2" />
            <span>general</span>
          </li>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('sales')}>
            <Lock size={20} className="mr-2" />
            <span>sales</span>
          </li>
          <li className="flex items-center py-2" onClick={() => handleChannelSelect('The Home Team')}>
            <div className="w-5 h-5 bg-green-500 rounded-full mr-2"></div>
            <span>The Home Team</span>
          </li>
        </ul>
      </div>
    </>
  );

  const ChannelView = () => (
    <div className="flex-1 flex flex-col">
      <div className="bg-white border-b border-gray-200 p-4 flex items-center justify-between">
        <div className="flex items-center">
          <ArrowLeft size={24} className="mr-4 text-gray-600" onClick={() => setCurrentView('home')} />
          <h2 className="font-bold text-lg">#{selectedChannel}</h2>
          <span className="ml-2 text-sm text-gray-500">3メンバー</span>
        </div>
        <div className="flex items-center space-x-4">
          <UserPlus size={20} className="text-gray-600" />
          <Link size={20} className="text-gray-600" />
        </div>
      </div>
      <div className="flex-1 p-4 overflow-y-auto bg-white">
        {messages.map(message => (
          <div key={message.id} className="mb-4">
            <div className="flex items-center mb-1">
              <div className="w-8 h-8 bg-gray-300 rounded-full mr-2"></div>
              <span className="font-bold">{message.user}</span>
              <span className="text-sm text-gray-500 ml-2">{message.time}</span>
            </div>
            <p className="ml-10">{message.content}</p>
            {message.reactions && (
              <div className="ml-10 mt-1">
                <span className="bg-gray-100 text-sm px-2 py-1 rounded-full">
                  👍 {message.reactions[0].count}
                </span>
              </div>
            )}
          </div>
        ))}
      </div>
      <div className="p-4 border-t border-gray-200 bg-white">
        <div className="bg-gray-100 rounded-full p-2 flex items-center">
          <input
            type="text"
            placeholder={`#${selectedChannel}にメッセージを送信`}
            className="bg-transparent flex-1 outline-none"
          />
          <MessageCircle size={20} className="text-gray-400 ml-2" />
        </div>
      </div>
    </div>
  );

  return (
    <div className="flex flex-col h-screen bg-white text-gray-800">
      <header className="bg-purple-700 text-white p-4 flex items-center justify-between">
        <div className="flex items-center">
          <div className="w-8 h-8 bg-yellow-400 rounded-md flex items-center justify-center mr-2">
            <span className="text-purple-700 font-bold">A</span>
          </div>
          <h1 className="font-bold text-lg">Anthropic Inc</h1>
        </div>
        <div className="w-8 h-8 bg-purple-600 rounded-full overflow-hidden">
          <img src="/api/placeholder/32/32" alt="Profile" className="w-full h-full object-cover" />
        </div>
      </header>

      <div className="bg-purple-700 px-4 pb-4">
        <div className="bg-purple-600 rounded-md flex items-center p-2">
          <Search size={20} className="text-purple-300 mr-2" />
          <input
            type="text"
            placeholder="Search..."
            className="bg-transparent text-white placeholder-purple-300 flex-1 outline-none"
          />
        </div>
      </div>

      <div className="flex-1 overflow-y-auto">
        {currentView === 'home' ? <HomeView /> : <ChannelView />}
      </div>

      <nav className="bg-white border-t border-gray-200 flex justify-around p-4">
        <button className="flex flex-col items-center">
          <Home size={24} />
          <span className="text-xs mt-1">Home</span>
        </button>
        <button className="flex flex-col items-center">
          <MessageCircle size={24} />
          <span className="text-xs mt-1">DMs</span>
        </button>
        <button className="flex flex-col items-center">
          <Bell size={24} />
          <span className="text-xs mt-1">Activity</span>
        </button>
      </nav>

      <button className="absolute bottom-20 right-4 w-14 h-14 bg-purple-600 rounded-full flex items-center justify-center shadow-lg">
        <MessageCircle size={24} className="text-white" />
      </button>
    </div>
  );
};

export default AnthropicSlackUI;

ClaudeのチャットUIを作成するTips

続いて、Claude Artifacts機能のチャットUIを作成するポイントを紹介します!

1. 基本的なチャットUIの作成

これには以下の要素が含まれています:
- メッセージ表示エリア
- 入力フィールド
- 送信ボタン

2.スタイリングの調整

UIをより魅力的にするため、以下のようなスタイリングを行いました:
- メッセージの吹き出しデザイン
- 背景色の設定(クリーム色)
- 送信ボタンのカスタマイズ(丸みを帯びた四角形、赤みがかった茶色)

3.コード生成機能の実装

「コード生成」というキーワードに反応して、サンプルコードを生成する機能を追加しました。

4.コード表示エリアの作成

生成されたコードを表示するための専用エリアを作成しました:
- 黒背景に緑文字のターミナル風デザイン

5.アニメーション効果の追加

UIをより動的で魅力的にするため、アニメーション効果を追加しました:
- ボットの返答を1文字ずつ表示 - コードも1文字ずつ表示

6.レスポンシブデザインの適用

様々な画面サイズに対応するため、レスポンシブデザインを適用しました。

7.細かな調整

ユーザーフィードバックを基に、以下のような細かな調整を行いました:
- メッセージの配置を左揃えに統一
- ユーザーとボットのメッセージの色分け
- プレースホルダーテキストの変更

まとめ

チャットアプリとClaude Artifacts機能は相性抜群ですね!今後ともClaudeに関する知見を発信していきますので、よければフォローお願いします!

ClaudeのチャットUIを作成するコード全体

ここから先は

5,715字

期間限定 PayPay支払いすると抽選でお得に!

この記事が参加している募集

#創作大賞2024

書いてみる

締切:

この記事が気に入ったらサポートをしてみませんか?