Claude 3.5 Sonnet の Artifacts 機能とは？使い方を分かりやすく解説！

## はじめに
Claude 3.5 Sonnet　が先月公開されました。

https://www.anthropic.com/news/claude-3-5-sonnet

Claude 3.5 Sonnetは、Anthropic社が開発した最新の大規模言語モデルです。前バージョンと比較して、大幅な性能向上が見られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/593ecf22-400f-cfff-ee34-62f403d91300.png)

注目なのは**Artifacts**という機能です。LLMが生成したコードを右側の実行環境でプレビューしつつ、対話形式で修正を依頼することができます。従来のチャットベースのやり取りに加え、より視覚的で直感的な開発体験を提供します。
今回はこのArtifacts機能を使った例をご紹介します。

## アプリのフロントを作成し、プレビューしながら修正する
Reactで作成されたコードをpreviewしつつ修正できます。
```
次のようなアプリを生成してください
1. 英語の入力ボックスに英文を入力する
2. 入力した英文は、クリック可能なUIとなる
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/c3062168-46d8-93f2-2a15-18b3c448f35f.png)

上記のように指示を出すと、Claude 3.5 Sonnetはリアルタイムにコードを生成し、右側のプレビューエリアに反映してくれます。
一発では狙い通り完成しませんが、プレビューを見ながら具体的な指示をチャットで伝えることで、コードを修正していくことができます。

何度か対話を続けることで、以下のようなところまで作成できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/5d2511ae-53fe-2dff-0ba7-af8ba419836d.png)

このコードをローカルの環境にコピーし、裏側で翻訳APIを繋げます。
今回はGoogle Cloud のCloud Translation APIを使用しました。
バックエンド部分は非同期処理で同期的に待ってしまったりと、ところどころ実装が不十分でしたが、
フロントの知識が皆無でも、Claude 3.5 Sonnetの力を借りることで、以下のような英語学習アプリが作成できました。

![英語翻訳アプリ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/a67731e6-e110-b4f3-75ea-6315d950fce0.gif)

具体的なコードは以下のようになりました。

```javascript
import React, { useState, useCallback } from 'react';
import axios from 'axios';

// Mock translation dictionaries
let wordTranslations = {
    'Hello': 'こんにちは',
    'world': '世界',
    'This': 'これは',
    'is': 'です',
    'a': '1つの',
    'test': 'テスト',
    'sentence': '文',
    'for': 'のための',
    'translation': '翻訳',
    'I': '私は',
    'love': '愛します',
    'programming': 'プログラミング',
    'in': 'で',
    'JavaScript': 'ジャバスクリプト',
};

let fullTextTranslations = {
    'Hello world': 'こんにちは、世界',
    'This is a test sentence for translation': 'これは翻訳のためのテスト文です',
    'I love programming in JavaScript': '私はJavaScriptでのプログラミングが大好きです',
};

// Google Cloud Translation APIを使用した翻訳関数
const translateTextWithAPI = async (text) => {
    const apiKey = '';  // ここに取得したAPIキーを入力してください
    const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;

    try {
        const response = await axios.post(url, {
            q: text,
            target: 'ja'
        });
        console.log("text: %s, translated: %s", text, response.data.data.translations[0].translatedText);
        return response.data.data.translations[0].translatedText;
    } catch (error) {
        console.error('Error translating text:', error);
        return '翻訳に失敗しました';
    }
};

const translateTextWithAPIbyword = async (word) => {
    const translatedText = await translateTextWithAPI(word);
    wordTranslations[word.toLowerCase()] = translatedText;
}
// Simulated API call for word translation
const translateWord = async (word) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log("keyとなるword: %s", word);
            const translation = wordTranslations[word.toLowerCase()] || `[${word}の訳]`;
            wordTranslations[word.toLowerCase()] = translation;
            resolve(translation);
        }, 100);
    });
};

// Simulated API call for full text translation
const translateFullText = async (text) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            const translation = fullTextTranslations[text] || text.split(' ').map(word => wordTranslations[word.toLowerCase()] || `[${word}の訳]`).join(' ');
            fullTextTranslations[text.toLowerCase()] = translation;
            resolve(translation);
        }, 500);
    });
};

const Word = ({ word }) => {
    const [showTranslation, setShowTranslation] = useState(false);

    const handleClick = () => {
        setShowTranslation(!showTranslation);
    };

    return (
        <span className="inline-block mr-1 mb-2">
            <span
                onClick={handleClick}
                className="cursor-pointer hover:bg-blue-100 p-1 rounded"
            >
                {word}
            </span>
            {showTranslation && (
                <span className="block text-sm text-gray-600">
                    {wordTranslations[word.toLowerCase()] || `[${word}の訳]`}
                </span>
            )}
        </span>
    );
};

const InteractiveTranslatorApp = () => {
    const [inputText, setInputText] = useState('');
    const [displayText, setDisplayText] = useState('');
    const [fullTranslation, setFullTranslation] = useState('');
    const [isLoading, setIsLoading] = useState(false);

    const handleInputChange = useCallback((e) => {
        setInputText(e.target.value);
    }, []);

    const handleSubmit = useCallback(async (e) => {
        e.preventDefault();
        setIsLoading(true);
        console.log('送信ボタンがクリックされました');
        console.log('入力テキスト:', inputText);

        const words = inputText.split(' ');

        let results = [];
        for (let word of words) {
            results.push(translateTextWithAPIbyword(word));
        }
        await Promise.all(results);

        results = [];
        for (let word of words) {
            results.push(translateWord(word));
        }
        await Promise.all(results);

        setDisplayText(inputText);
        setIsLoading(false);
    }, [inputText]);

    const handleButtonClick = useCallback(() => {
        console.log('ボタンが直接クリックされました');
    }, []);

    const handleFullTranslation = useCallback(async () => {
        setIsLoading(true);
        const translatedText = await translateTextWithAPI(displayText);
        // const translatedText = await translateFullText(displayText);
        setFullTranslation(translatedText);
        setIsLoading(false);
    }, [displayText]);

    return (
        <div className="p-4 max-w-2xl mx-auto">
            <h1 className="text-2xl font-bold mb-4">インタラクティブ英語翻訳アプリ</h1>
            <form onSubmit={handleSubmit} className="mb-4">
                <textarea
                    value={inputText}
                    onChange={handleInputChange}
                    placeholder="英語の文章を入力してください"
                    className="w-full p-2 border border-gray-300 rounded"
                    rows="4"
                />
                <button
                    type="submit"
                    onClick={handleButtonClick}
                    className="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
                    disabled={isLoading}
                >
                    {isLoading ? '処理中...' : '送信'}
                </button>
            </form>
            {displayText && (
                <div className="mt-4">
                    <div className="p-4 bg-gray-100 rounded">
                        {displayText.split(' ').map((word, index) => (
                            <Word key={`${word}-${index}`} word={word} />
                        ))}
                    </div>
                    <button
                        onClick={handleFullTranslation}
                        className="mt-2 px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
                        disabled={isLoading}
                    >
                        {isLoading ? '処理中...' : '全体翻訳'}
                    </button>
                    {fullTranslation && (
                        <div className="mt-2 p-4 bg-yellow-100 rounded">
                            <h2 className="font-bold mb-2">全体翻訳:</h2>
                            <p>{fullTranslation}</p>
                        </div>
                    )}
                </div>
            )}
            <div className="mt-4">
                <p>デバッグ情報:</p>
                <p>入力テキスト: {inputText}</p>
                <p>表示テキスト: {displayText}</p>
                <p>全体翻訳: {fullTranslation}</p>
            </div>
        </div>
    );
};

export default InteractiveTranslatorApp;

```


## svg形式で図を作成し、プレビューを閲覧しながら調整
diffusionモデルを使った画像生成はできませんが、svg形式で画像を作成しプレビューすることができます。
```
svgで、会社のロゴを作成してください
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/df43c01c-a398-6019-2ff7-124f55f56eb8.png)

Claude 3.5 Sonnetは、抽象的な指示に対しても、具体的なアウトプットを生成することができます。もちろん、ロゴのデザインも調整可能です。

次に、JKKのロゴを作ってもらいます。
```
Create the logo of Juncture Keeps Key Technologies, Inc (JKK Technologies) in svg
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/523ac7ed-d1d3-d255-382f-772f53ab42a5.png)

会社名を与えることで、それを反映したロゴを作成してくれます。

もう少し会社情報を入れてみましょう。

```
JKK Technologies is a company that specializes in data analysis, so please create a logo that will make that feature easily recognizable!
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/61a54dce-6d60-e17a-5474-e0b434db1f95.png)

会社の特徴を伝えることで、ロゴにそれを象徴する要素を加えることも可能です。

なかなかかわいいロゴが作成できました。

## 終わりに
Claude 3.5 Sonnetは、Artifacts機能によって、従来のLLMよりもさらにインタラクティブで直感的な開発体験を提供します。今回の例を通じて、その可能性を感じていただけたのではないでしょうか。今後、Claude 3.5 Sonnetがどのように活用されていくのか、非常に楽しみです。