自家製WebアプリInterpreterを作る

# はじめに
ClaudeのArtifactsを見て、Webページを作成してくれるシナリオにビビっと来まして、以前は[Code Interpreterを自家製で再現](https://qiita.com/shyamagu/items/64450f96027bbf484c71)しましたが、今回はgpt-4oを用いてWebアプリInterpreterを作ってみたいと思います。

結果的に以下のようなWebInterpreterができました。以下で全体像や実装のポイントを記載します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/796ae594-ccf8-efde-ebf2-49f646de10ea.png)

# 全体像
実装には、Azure Static Web Appsと、Azure OpenAI Serviceを利用しました。どんなWeb画面が欲しいかのリクエストをもとに、gpt-4oでHTML/js/CSSを生成し、それを画面上でプレビュー表示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/c5886510-1e58-473d-e77e-c54e81b5f2b3.png)

# 実装のポイント

## 1. JSONモードでHTML/js/CSSを生成させる
Azure Functionsから実行するため @azure/openaiを利用します。フォーマットとしてJSONを指定することでJSONモードが利用できます。

```js
const params = {
    temperature: 0,
    responseFormat: { type: "json_object" }
};

const event = await client.streamChatCompletions(model, messages, params);
```

その上で、以下のようなプロンプトを使います。json形式で、コメント、HTML、JavaScript、CSSを出力指定します。今回はグラフも作成したいので、Chart.jsを利用させます。
ポイントは、生成させるJavaScriptの変数をスコープ内にするように、必ず関数内に記載させるところです。これをしないとグローバルスコープになってしまい複数回ロードができなくなることがあるためです。

```js
const system_prompt = `\
あなたはHTML/JavaScript/CSSを生成するエキスパートエンジニアリングAIです。
ユーザの要望に対して以下のようなjson形式の回答を出力します。

{
    "comment": ※出力HTML/JS/CSSに関する簡単なコメント
    "html": ※BODYタグ内のHTML(<body>は含みません)
    "script": ※SCRIPTタグ内のJavaScript(<script>は含みません、また必ず関数内に記載してください。ロード時実行が必要であれば関数定義と関数呼び出しを記載します)
    "style": ※STYLEタグ内のCSS(<style>は含みません)
}

### 制約
 - HTML/CSS部分にクラス名やIDが必要な場合は、必ずwebi_XXXXのようにwebi_で始まる名前を付けてください。
 - グラフ描画の要望の場合は、Chart.jsの実装で回答してください。
 - ヘッダーが必要な場合は、DIV要素として実装してください。
 - フッターは不要です。フッターは作成しないでください。
 - ページ遷移は実装できません。1画面のみの実装としてください。

もしアイディアディスカッションなども含め、通常の対話のみでHTML/JavaScript/CSSが存在しない場合はそれぞれ空文字を返してください。
`
```

<details>
  <summary>サンプルを含めたプロンプトの全文はこちら</summary>

```js

            const system_prompt = `\
あなたはHTML/JavaScript/CSSを生成するエキスパートエンジニアリングAIです。
ユーザの要望に対して以下のようなjson形式の回答を出力します。

{
    "comment": ※出力HTML/JS/CSSに関する簡単なコメント
    "html": ※BODYタグ内のHTML(<body>は含みません)
    "script": ※SCRIPTタグ内のJavaScript(<script>は含みません、また必ず関数内に記載してください。ロード時実行が必要であれば関数定義と関数呼び出しを記載します)
    "style": ※STYLEタグ内のCSS(<style>は含みません)
}

### 制約
 - HTML/CSS部分にクラス名やIDが必要な場合は、必ずwebi_XXXXのようにwebi_で始まる名前を付けてください。
 - グラフ描画の要望の場合は、Chart.jsの実装で回答してください。
 - ヘッダーが必要な場合は、DIV要素として実装してください。
 - フッターは不要です。フッターは作成しないでください。
 - ページ遷移は実装できません。1画面のみの実装としてください。

もしアイディアディスカッションなども含め、通常の対話のみでHTML/JavaScript/CSSが存在しない場合はそれぞれ空文字を返してください。

### サンプル出力1: アラートボタン付きWebページ
{
    "comment": "アラートボタン付きWebページを生成しました",
    "html": "<div class="webi_title">サンプルタイトル</div>
<button type="button" onclick="ShowAlert()">ここをクリック</button>",
    "script": "function ShowAlert() {alert("Hello world!!");}",
    "style": ".webi_title { font-size: 2.0em; color: red; }"
}

### サンプル出力2: グラフ描画Webページ
{
    "comment": "グラフを描画するWebページを生成しました",
    "html": "<div>
  <canvas id="webi_myChart"></canvas>
</div>",
    "script": "function showGraph() {
  const ctx = document.getElementById('webi_myChart');

  new Chart(ctx, {
    type: 'bar',
    data: {
      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
      datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderWidth: 1
      }]
    },
    options: {
      scales: {
        y: {
          beginAtZero: true
        }
      }
    }
  });
}
showGraph();",
    "style": ""
}

### サンプル出力3: 対話のみ
{
    "comment": "ご認識の通りです。",
    "html": "",
    "script": "",
    "style": ""
}
`
```
</details>

※プロンプトは、とりあえずの動作確認までしかしておらず、ブラッシュアップをしていない状態です。

## 2. Webページをプレビュー表示する

受け取ったJSONをもとにSvelteの画面側でロードさせていきます。生成されたJavaScriptをそのまま利用可能にしているためセキュリティ的には微妙なところです。ちゃんとやるのであれば、生成されたJavaScriptがセキュアかどうかをgptに評価させたりというのもアリかもしれません。

ロードする部分のみを抜粋したものが以下です。JSONで取得したWebページの内容を組み立てます。
```svelte
<script>
    function loadWebPage(str_html, str_js, str_style) {

        const artifact = document.getElementById('artifact');

        if(artifact){
            if(html_node) artifact.removeChild(html_node);
            if(js_node) artifact.removeChild(js_node);
            if(style_node) artifact.removeChild(style_node);
        }

        html_node = document.createElement('div');
        html_node.innerHTML = str_html;
        artifact.appendChild(html_node);

        js_node = document.createElement('script');
        js_node.textContent = str_js;
        artifact.appendChild(js_node);

        style_node = document.createElement('style');
        style_node.textContent = str_style;
        artifact.appendChild(style_node);

    }
</script>

<div class="browser_header" >ー □ ｘ</div>
<div class="preview">
    <div id="artifact"></div>
</div>
```

# できたもの再び

タイトル”電卓”をいれてみました。なかなかよくできたWebページを生成してくれます。ただしHTML上でロードするためにフロントエンドフレームワークは使っていない成果物が出力されます。

そこでSvelteコンポーネントに変換するボタンも実装上は用意してみました。これによって画面をちょこちょこ作ったうえでSvelteコンポーネントに変換して利用することができるので、UI開発がまた便利になりそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/8261d52a-0bfa-b074-ed64-bbde8279ed3c.png)

# おわりに

Claude3.5のArtifactsにインスパイアされてWebアプリinterpreterを実装してみました。画面モックを作るときなど、かなり使えるシーンがあるんじゃないかしらというのが感想です。Svelteコンポーネントに変換するなど、自分で必要な処理を気軽に追加できるのが自家製のメリットかと思いますし、データ分析もある程度できそうです。

しかし生成AIの利用シナリオはまだまだありそうですね。今回のも気づかずじまいでした。類似シナリオとしてテストシナリオやテストコードの作成Interpreterですとか、その他にも業務シナリオベースでというところも色々ありそうです。

今後も普段の作業を見直して生成AIが使えるところを探していきたいと思いますし、サービスとして実現されたものなどもキャッチアップしてアイディアを探し続けていきたいと思いました。