【Amazon Bedrock】【Node.js】生成AIを使用したVBAフォーマッター

プロジェクトの内容

Amazon Bedrockの生成AIを活用した、VBAマクロコードを自動的にフォーマットするWebアプリケーション。インデント、変数名、コメントなどを統一的なルールで整形します。

機能概要

  • VBAコードのフォーマット
  • インデントの自動調整
  • 変数名の標準化
  • コメントの追加
  • ヘッダー・フッターの挿入

技術スタック

  • Node.js
  • Express.js
  • HTML/CSS/JavaScript

必要要件

  • Node.js (v14以上)
  • npm (v6以上)

セットアップ手順

  1. プロジェクト構成ファイルを作成
  2. 依存パッケージのインストール
npm install
  1. 開発サーバーの起動
npm run dev
  1. ブラウザでアクセス
http://localhost:3000

プロジェクト構成

vba-formatter/
├── src/
│   ├── index.js           # メインサーバーファイル
│   ├── formatter.js       # 基本的なフォーマット処理
│   ├── bedrock/          # Bedrock関連の新規ディレクトリ
│   │   ├── client.js     # Bedrock クライアント設定
│   │   ├── prompts.js    # プロンプトテンプレート
│   │   └── analyzer.js   # コード分析ロジック
│   └── public/           # フロントエンド

Bedorock関連

.env

AWS_ACCESS_KEY_ID=あなたのアクセスキー
AWS_SECRET_ACCESS_KEY=あなたのシークレットキー
AWS_REGION=us-east-1

dotenvは、.envファイルを読み込んで、その内容を環境変数としてprocess.envに設定するためのパッケージです。

npm install dotenv

@aws-sdk/client-bedrock-runtime: Amazon Bedrockと通信するため下記インストール

npm install dotenv @aws-sdk/client-bedrock-runtime
client.js:Amazon Bedrockへの接続設定
require('dotenv').config();
const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");

// Bedrockクライアントの設定
const client = new BedrockRuntimeClient({ 
    region: process.env.AWS_REGION || "us-east-1",
    credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
    }
});

module.exports = client;
prompts.js:AIへの指示(プロンプト)テンプレート
/**
* prompts.js
* AIへの指示内容を定義するファイル
* VBAコードの分析と改善の方法をAIに指示します
*/

// AIへの指示内容を定義(バッククォートで囲むことで複数行の文字列を作成)
const ANALYSIS_PROMPT = `
# ここからがAIへの指示内容です
=============================================

# AIの役割定義
あなたはVBAコードの専門家として、以下のコードを分析し、優先順位に従って改善してください。

# 入力コード部分
\`\`\`vba
{code}
\`\`\`

# 改善要件(優先順位:高 - 第一弾)
=============================================
1. インデント処理
  - SUBからENDSUBまでTABひとつ分のインデント
  - IF文、With文、For文等の制御構造にもインデント
  - ネストレベルに応じて適切なインデント

2. ヘッダー・フッター追加
  - Sub開始時のヘッダー:
    Option Explicit
    '***************************************************
    ' [プロシージャ名]
    '***************************************************
    '【機  能】[機能の説明]
    '【引  数】[引数の説明]
    '【戻 り 値】[戻り値の説明]
    '【機能説明】[詳細な説明]
    '【備  考】[その他特記事項]
    ' Copyright(c) 2024 Your Company All Rights Reserved.
    '***************************************************
    
  - 処理の区切り:
    '***************************************************
    ' 変数宣言
    '***************************************************
    [変数宣言部分]

  - Sub終了時のフッター:
    '***************************************************
    '---------------------------------------------------
    ' Version
    '---------------------------------------------------
    ' 1.00 | 2024.01.01 | ********* | *********
    '***************************************************

# 改善要件(優先順位:中 - 第二弾)
=============================================
3. 変数名の改善
  - 一文字の変数を禁止
  - セル参照の変数は意味のある名前に変更
    (例: Last_Row, Last_Column)
  - 配列はArrayを付ける
  - その他の変数はDataを付ける

4. Public変数・Call文の処理
  - Public変数にはPublic関数であることをコメントで記載
  - Call文にはモジュール名を追加
    (例: Call Module1.印刷)

5. 変数宣言のコメント
  - 宣言した変数の横にコメントを追加
  - 使用目的や内容を簡潔に説明

# 改善要件(優先順位:中 - 第三弾)
=============================================
6. 変数・シート名の処理
  - 日本語変数名を英語に変更
  - 未宣言変数を「変数宣言」セクションに追加
  - 型判定できない変数はVariantに
  - シート名は定数(Const)で定義
  - シート追加時の名前はVariantで処理

7. コメント追加
  - IF文、With文などの制御構造にコメント
  - 配列の内容説明
  - SET文の説明
  - Offsetのコメント必須
    (参照セルと目的を明記)

8. コードの最適化
  - 類似コードが3回以上続く場合はループ化
  - パスの直書きは避け、pathに置換
    (元のパスはコメントとして保持)

# 出力形式
=============================================
優先順位の高い要件(第一弾)から順に適用し、
改善したVBAコードのみを出力してください。
`;

module.exports = {
   ANALYSIS_PROMPT
};
analyzer.js:Bedrockとの通信処理
// AWS SDKからBedrockのクライアントとコマンドをインポート
const { InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");
const client = require('./client');
const { ANALYSIS_PROMPT } = require('./prompts');

/**
 * VBAコードをClaudeモデルで分析する関数
 * @param {string} vbaCode - 分析対象のVBAコード
 * @returns {Promise<string>} - 改善されたVBAコード
 */
async function analyzeCode(vbaCode) {
    try {
        // プロンプトテンプレートにVBAコードを挿入
        const prompt = ANALYSIS_PROMPT.replace('{code}', vbaCode);

        // Claude用のリクエストコマンドを作成
        const command = new InvokeModelCommand({
            // Claude 3 Sonnetモデルを指定
            modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
            contentType: "application/json",
            accept: "application/json",
            // Claude用のリクエスト形式
            body: JSON.stringify({
                anthropic_version: "bedrock-2023-05-31",
                max_tokens: 2048,
                messages: [
                    {
                        role: "user",
                        content: prompt
                    }
                ]
            })
        });

        // BedrockAPIを呼び出し
        const response = await client.send(command);
        
        // レスポンスの処理(Claude形式)
        const responseBody = Buffer.from(response.body).toString('utf8');
        const jsonResponse = JSON.parse(responseBody);

        // Claudeのレスポンス形式から結果を抽出
        if (jsonResponse.content && jsonResponse.content[0]) {
            return jsonResponse.content[0].text;
        }

        throw new Error('AIモデルからの応答が不正な形式です');
    } catch (error) {
        console.error("コード分析エラー:", error);
        throw error;
    }
}

module.exports = {
    analyzeCode
};

メインファイル

src/index.js:Webサーバーのメイン処理
// ========================================
// サーバーのメインファイル (index.js)
// VBAフォーマッターのバックエンド処理を担当
// ========================================

// 必要なモジュールのインポート
const express = require('express');
const { analyzeCode } = require('./bedrock/analyzer');  // Bedrock AIの分析機能
const { formatVBA } = require('./formatter'); // 基本的なフォーマット機能
const { ANALYSIS_PROMPT } = require('./bedrock/prompts'); // AIプロンプトの取得

// Expressアプリケーションの初期化
const app = express();

// ミドルウェアの設定
// 静的ファイル(HTML/CSS/JS)の提供設定
app.use(express.static('src/public')); 
// JSONリクエストの解析を有効化
app.use(express.json());

/**
 * AIへのプロンプト内容を提供するエンドポイント
 * フロントエンドでAIへの指示内容を表示するために使用
 */
app.get('/prompts', (req, res) => {
    res.json({ 
        success: true, 
        prompt: ANALYSIS_PROMPT 
    });
});

/**
 * VBAコード分析API
 * フロントエンドから送信されたコードを分析・改善
 */
app.post('/format', async (req, res) => {
    try {
        // リクエストからコードを取得
        const { code } = req.body;
        
        // 入力チェック
        if (!code) {
            return res.status(400).json({ 
                success: false, 
                error: 'コードが提供されていません' 
            });
        }

        // 1. 基本的なフォーマットを適用
        const basicFormatted = formatVBA(code);

        // 2. AIによる高度な分析と改善
        const aiAnalysis = await analyzeCode(basicFormatted);
        
        // 成功時のレスポンス
        res.json({ 
            success: true, 
            formatted: aiAnalysis
        });

    } catch (error) {
        // エラー発生時の処理
        console.error('エラーが発生しました:', error);
        res.status(500).json({ 
            success: false, 
            error: error.message 
        });
    }
});

// サーバーの起動設定
const PORT = process.env.PORT || 3000;

// 全てのIPアドレスからのアクセスを許可(社内ネットワーク用)
app.listen(PORT, '0.0.0.0', () => {
    console.log('========================================');
    console.log(`VBA Formatter サーバー起動`);
    console.log(`http://localhost:${PORT}`);
    console.log('========================================');
});

フロントエンド

public/index.html:ユーザーインターフェース
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 文字エンコーディングとビューポートの設定 -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VBA Formatter</title>
    <!-- reset.css ress -->
    <link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css" />
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="stylesheet" href="style.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
    <!-- スタイルシートの読み込み -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- メインコンテナ -->
    <div class="container">
        <h1>VBA Formatter</h1>

        <!-- AIへの指示内容表示セクション(src/bedrock/prompts.jsの内容) -->
        <div class="prompt-section">
            <h3>AIへの指示内容</h3>
            <!-- プロンプト内容を動的に表示する要素 -->
            <div class="prompt-display-container">
                <div class="user-icon"></div>
                <div class="prompt-display-wrapper">
                    <pre class="prompt-display">ユーザーからの指示内容がここに表示されます</pre>
                </div>
            </div>
        </div>

        <!-- コードエディタ部分 -->
        <div class="editors">
            <!-- 入力用エディタ -->
            <div class="editor">
                <h3>分析するコード</h3>
                <!-- 入力用テキストエリア:ユーザーがコードを入力または貼り付け可能 -->
                <textarea id="input" placeholder="VBAコードを入力してください"></textarea>
                
                <!-- ファイル選択ボタン:.basと.txtファイルのみ受付 -->
                <input type="file" id="vbaFile" accept=".bas,.txt">
                <!-- <span class="detail">※.basと.txtファイルのみ受付</span> -->
            </div>
            <!-- 出力用エディタ -->
            <div class="editor">
                <h3>改善後のコード</h3>
                <!-- 出力用テキストエリア:読み取り専用で改善されたコードを表示 -->
                <textarea id="output" readonly></textarea>
                
                <!-- フォーマット実行ボタン -->
                <button onclick="formatCode()">Format</button>
            </div>
        </div>
    </div>

    <!-- JavaScriptの処理 -->
    <script>
        // ページ読み込み時にプロンプト内容を取得
        window.onload = async function() {
            try {
                const response = await fetch('/prompts');
                const data = await response.json();
                if (data.success) {
                    document.querySelector('.prompt-display').textContent = data.prompt;
                }
            } catch (error) {
                console.error('プロンプト取得エラー:', error);
                document.querySelector('.prompt-display').textContent = 'プロンプトの読み込みに失敗しました';
            }
        };

        /**
         * コードのフォーマットを実行する関数
         * 1. 入力を取得
         * 2. サーバーにリクエスト
         * 3. 結果を表示
         */
        async function formatCode() {
            // テキストエリアから入力を取得
            const input = document.getElementById('input').value;
            // ステータス表示用の要素取得
            const formatButton = document.querySelector('button');
            
            // 入力値のバリデーション
            if (!input) {
                alert('VBAコードを入力してください');
                return;
            }

            // 処理開始のステータス表示
            formatButton.innerHTML = '<span class="processing"><span></span><span></span><span></span></span>';

            try {
                // サーバーへのリクエスト
                const response = await fetch('/format', {
                    method: 'POST',  // POSTメソッドを使用
                    headers: {
                        'Content-Type': 'application/json'  // JSONデータとして送信
                    },
                    body: JSON.stringify({ code: input })  // 入力をJSONに変換
                });

                // レスポンスの処理
                const data = await response.json();
                
                // 成功時は結果を表示、失敗時はエラーメッセージを表示
                if (data.success) {
                    document.getElementById('output').value = data.formatted;
                    formatButton.textContent = '分析完了!';
                } else {
                    formatButton.textContent = 'エラー: ' + data.error;
                }
            } catch (error) {
                // 通信エラーなどの例外処理
                formatButton.textContent = 'エラー: ' + error.message;
            }
            formatButton.classList.remove('processing'); // アニメーションクラスを削除
        }

        /**
         * ファイル選択時の処理
         * 選択されたファイルの内容を入力エリアに表示
         */
        document.getElementById('vbaFile').addEventListener('change', (e) => {
            // 選択されたファイルを取得
            const file = e.target.files[0];
            if (file) {
                // FileReaderを使用してファイルの内容を読み込み
                const reader = new FileReader();
                // ファイル読み込み完了時の処理
                reader.onload = (e) => {
                    // 読み込んだ内容を入力エリアに設定
                    document.getElementById('input').value = e.target.result;
                };
                // ファイルをテキストとして読み込み開始
                reader.readAsText(file);
            }
        });
    </script>
</body>
</html>
public/style.css:見た目の装飾
body {
    font-family: "Noto Sans JP", serif;
      background-color: #f1f2f3;
  }
  /* メインコンテナ */
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 2rem 2rem;
  }
  
  h1 {
      background-color: #fff;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      text-align: center;
      letter-spacing: 0.1em;
      padding: 1rem 0;
      margin: 0 calc(50% - 50vw);
      width: 100vw;
      margin-bottom: 2rem;
  }
  
  h3 {
    margin-bottom: 1rem;
      text-align: center;
      letter-spacing: .1em;
  }
  
  /* AIへの指示内容表示部分 */
  .prompt-section {
    margin-bottom: 2rem;
  }
  
  .prompt-display-container {
      display: flex;
    align-items: end;
      justify-content: center;
    gap: 2rem;
      margin: 0 auto;
  }
  
  .user-icon {
    width: 40px;
    height: 40px;
    background-color: #f6ce55;
    border-radius: 50%;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      margin-bottom: 3rem;
      position: relative;
  }
  
  .user-icon::after {
    content: "";
    position: absolute;
    top: 96%;
    left: 50%;
    transform: translateX(-50%);
    width: 50px;
    height: 40px;
    background-color: #f6ce55;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    border-top-left-radius: 50%;
    border-top-right-radius: 50%;
  }
  
  .prompt-display-wrapper {
      max-width: 80%;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #fff;
    border-radius: 5rem 5rem 5rem 0;
    box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
      padding: 2rem;
  }
  
  .prompt-display {
    font-family: monospace;
    font-size: 0.825rem;
    line-height: 1.5;
    white-space: pre-wrap;
      height: 300px;
      overflow-y: scroll;
      resize: vertical;
  }
  
  /* カスタムスクロールバーのスタイル */
  .prompt-display::-webkit-scrollbar {
    width: 0.5rem; /* スクロールバーの幅 */
  }
  
  .prompt-display::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 1rem;
  }
  
  /* エディタ部分 */
  .editors {
    display: flex;
    gap: 2rem;
      margin-bottom: 1rem;
  }
  
  .editor {
    flex: 1;
      display: flex;
      flex-direction: column;
  }
  
  .editor > * {
      margin-bottom: 1rem;
  }
  
  .editor > *:last-child {
      margin-top: auto!important;
      }
  
  .editor input {
      height: 3rem;
      background-color: #ccc;
    padding: 0.5rem 1rem;
    border-radius: 4px;
      width: 100%;
      transition: all 0.3s ease;
  }
  
  .editor input:hover {
      background-color: #bbb;
  }
  
  .editor .detail {
      margin-top: 0.5rem;
      color: #FF565D;
  }
  
  /* テキストエリア */
  .editor textarea {
    width: 100%;
    height: 500px;
    font-family: monospace;
    padding: 10px;
    font-size: 14px;
    line-height: 1.5;
    border: 2px solid #ccc;
      background-color: #fff;
    border-radius: 4px;
  }
  
  button {
      height: 3rem;
    padding: 0.5rem 1rem;
    background-color: #e9be3b;
      ;
    border-radius: 4px;
    color: white;
      letter-spacing: 0.1em;
    border: none;
    cursor: pointer;
  }
  
  button:hover {
    background-color: #d3b458;
      ;
      transition: all 0.3s ease;
  }
  
  /* アニメーションを追加するクラス */
  .processing {
    display: inline-flex;
    gap: 0.5rem;
  }
  
  .processing span {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    background-color: #fff;
    border-radius: 50%;
    animation: fadeDots 1.5s infinite ease-in-out;
  }
  
  /* それぞれのドットに異なるアニメーションの遅延を設定 */
  .processing span:nth-child(1) {
    animation-delay: 0s;
  }
  
  .processing span:nth-child(2) {
    animation-delay: 0.2s;
  }
  
  .processing span:nth-child(3) {
    animation-delay: 0.4s;
  }
  
  /* 薄さ(透明度)を変化させるアニメーション */
  @keyframes fadeDots {
    0%, 100% { opacity: 0.2; } /* 薄くなる */
    50% { opacity: 1; } /* 濃くなる */
  }
  
  

その他

formatter.js:VBAコードの基本的な整形処理
// VBAコードをフォーマットする関数
// 引数:code - フォーマットする元のVBAコードの文字列
// 戻り値:フォーマット済みのVBAコードの文字列
function formatVBA(code) {
    // ステップ1: コードを行単位に分割
    // splitは文字列を指定した区切り文字(ここでは改行)で配列に分割する
    const lines = code.split('\n');
    
    // ステップ2: インデントレベルを管理する変数を初期化
    // インデントレベルは、コードのネストの深さを表す
    // 例:Sub内なら1、If文の中なら2、というように増えていく
    let indentLevel = 0;
    
    // ステップ3: フォーマット済みの行を格納する配列を準備
    const formattedLines = [];

    // ステップ4: キーワードを定義
    // これらの単語が行の先頭にあるかどうかでインデントを判断する
    const startKeywords = ['sub', 'function', 'if', 'for', 'do'];
    const endKeywords = ['end sub', 'end function', 'end if', 'next', 'loop'];

    // ステップ5: 各行を処理
    // forEachは配列の各要素に対して処理を行う
    lines.forEach((line, lineNumber) => {
        // 行の空白を削除して、小文字に変換(比較用)
        const trimmedLine = line.trim();
        const lowerLine = trimmedLine.toLowerCase();

        // ステップ6: 空行の処理
        if (trimmedLine === '') {
            formattedLines.push('');
            return; // 次の行の処理へ
        }

        // ステップ7: インデントレベルの調整
        // 7.1: ブロックの終了を示すキーワードの前にインデントを減らす
        if (endKeywords.some(keyword => lowerLine.startsWith(keyword))) {
            indentLevel = Math.max(0, indentLevel - 1);
        }

        // ステップ8: 行のフォーマット
        // 8.1: 現在のインデントレベルに応じてスペースを追加
        // repeat()は文字列を指定回数繰り返す
        const indent = '    '.repeat(indentLevel);
        
        // 8.2: コメントの処理
        // シングルクォートで始まる行はコメント
        if (trimmedLine.startsWith("'")) {
            formattedLines.push(indent + trimmedLine);
        } else {
            // 8.3: 通常のコードの処理
            formattedLines.push(indent + trimmedLine);
        }

        // ステップ9: 次の行のためのインデントレベルの更新
        // 9.1: ブロックの開始を示すキーワードの後でインデントを増やす
        if (startKeywords.some(keyword => lowerLine.startsWith(keyword))) {
            indentLevel++;
        }
    });

    // ステップ10: フォーマット済みの行を改行文字で結合して1つの文字列に戻す
    return formattedLines.join('\n');
}

// ステップ11: ヘッダーコメントを生成する関数
function createHeader() {
    return [
        "Option Explicit",
        "'***************************************************",
        "' VBAプログラム",
        "'***************************************************",
        "'【機  能】",
        "'【返 り 値】なし",
        "'【機能説明】",
        "'【備  考】",
        "' Copyright(c) " + new Date().getFullYear(),
        "'***************************************************",
        ""
    ].join('\n');
}

// ステップ12: 変数をチェックして適切な型を提案する関数
function suggestVariableType(variableName) {
    // 変数名から型を推測
    if (variableName.toLowerCase().includes('count')) {
        return 'Long';
    } else if (variableName.toLowerCase().includes('date')) {
        return 'Date';
    } else if (variableName.toLowerCase().includes('flag')) {
        return 'Boolean';
    } else if (variableName.toLowerCase().includes('name')) {
        return 'String';
    } else {
        return 'Variant';
    }
}

// ステップ13: モジュールの外部公開
// このファイルの関数を他のファイルから使用できるようにする
module.exports = {
    formatVBA,
    createHeader,
    suggestVariableType
};

プロジェクト設定ファイル

package.json:依存パッケージの管理
{
    "name": "vba-formatter",
    "version": "1.0.0",
    "description": "VBA Code Formatter",
    "main": "src/index.js",
    "scripts": {
        "start": "node src/index.js",
        "dev": "nodemon src/index.js"
    },
    "dependencies": {
        "@aws-sdk/client-bedrock-runtime": "^3.712.0",
        "dotenv": "^16.4.7",
        "express": "^4.18.2",
        "multer": "^1.4.5-lts.1"
    },
    "devDependencies": {
        "nodemon": "^3.0.2"
    }
}
test-connection.js:正しく動作するかテスト
require('dotenv').config();
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");

async function testBedrock() {
    console.log("環境変数確認:");
    console.log("Region:", process.env.AWS_REGION);
    console.log("Access Key ID exists:", !!process.env.AWS_ACCESS_KEY_ID);
    console.log("Secret Access Key exists:", !!process.env.AWS_SECRET_ACCESS_KEY);

    const client = new BedrockRuntimeClient({
        region: process.env.AWS_REGION || "us-east-1",
        credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
        }
    });

    // モデルごとに適切なリクエスト本文を生成する関数
    function getRequestBody(modelId) {
        if (modelId.startsWith('amazon.titan')) {
            return {
                inputText: "Hello",
                textGenerationConfig: {
                    maxTokenCount: 10,
                    temperature: 0.7,
                    topP: 1
                }
            };
        } else if (modelId.startsWith('anthropic')) {
            return {
                anthropic_version: "bedrock-2023-05-31",
                max_tokens: 1024,
                messages: [
                    {
                        role: "user",
                        content: "Hello, Claude!"
                    }
                ]
            };
        }
    }

    // テストするモデルのリスト
    const modelIds = [
        // Titan Models(動作確認済み)
        "amazon.titan-text-express-v1",
        "amazon.titan-text-lite-v1",

        // Mistral Models
        "mistral.mistral-7b-instruct-v0:2",
        "mistral.mistral-large-2402-v1:0",
        "mistral.mistral-large-2407-v1:0",
        "mistral.mistral-small-2402-v1:0",
        "mistral.mixtral-8x7b-instruct-v0:1",

        // Claude Models
        "anthropic.claude-3-sonnet-20240229-v1:0",  // Claude 3 Sonnet
        "anthropic.claude-3-haiku-20240307-v1:0",   // Claude 3 Haiku
        "anthropic.claude-3-opus-20240229-v1:0",    // Claude 3 Opus
        "anthropic.claude-3-5-haiku-20241022-v1:0", // Claude 3.5 Haiku
        "anthropic.claude-3-5-sonnet-20241022-v2:0",// Claude 3.5 Sonnet v2
        "anthropic.claude-3-5-sonnet-20240620-v1:0",// Claude 3.5 Sonnet
        "anthropic.claude-v2:1",                    // Claude 2.1
        "anthropic.claude-v2",                      // Claude 2
        "anthropic.claude-instant-v1"               // Claude Instant
    ];

    // 各モデルをテスト
    for (const modelId of modelIds) {
        console.log(`\nテスト: ${modelId}`);
        try {
            const command = new InvokeModelCommand({
                modelId: modelId,
                contentType: "application/json",
                accept: "application/json",
                body: JSON.stringify(getRequestBody(modelId))
            });
            const response = await client.send(command);
            console.log(`✅ ${modelId} は利用可能です`);
            
            // レスポンスの内容を表示
            const responseBody = Buffer.from(response.body).toString('utf8');
            console.log('応答:', responseBody);
        } catch (error) {
            console.log(`❌ ${modelId}: ${error.message}`);
        }
    }
}

testBedrock();
node test-connection.js

使用方法

  1. Webブラウザで http://localhost:3000 にアクセス
  2. テキストエリアにVBAコードを貼り付け、または.basファイルを選択
  3. 「Format」ボタンをクリック
  4. 右側のテキストエリアにフォーマット済みコードが表示

要件定義

### VBA Formatter with Amazon Bedrock プロジェクト説明

#### プロジェクト概要
このプロジェクトはJavaScript言語でNode.jsを利用し、ExpressというWebアプリケーションフレームワークで作成されています。VBAコードを改善するためにAmazon BedrockのAIを活用しています。

#### ファイル構成と役割
1. `src/public/index.html`
   - Webブラウザで表示されるユーザーインターフェース
   - VBAコードの入力とAI改善結果の表示を担当

2. `src/index.js`
   - サーバープログラムのメインファイル
   - ブラウザからのリクエストを処理
   - AIとの連携を管理

3. `src/bedrock/prompts.js`
   - AIへの指示内容(プロンプト)を定義
   - コードの改善ルールを記述

4. `src/bedrock/analyzer.js`
   - Amazon BedrockのAIと通信
   - AIからの応答を処理

#### 処理の流れ
1. ブラウザでVBAコードを入力
2. サーバーがコードを受け取り
3. AIに分析を依頼
4. 改善されたコードが表示

#### 改善点
これまでAWSコンソールのAmazon Bedrockプレイグラウンドでチャット形式で行っていたAIとの対話を、使いやすいWebインターフェースで実現。以下が改善されました:
- コードの入力が簡単に
- AIへの指示内容が明確に表示
- 結果の表示が見やすく
- 複数のコードを連続して処理可能

#### 拡張性
- AIへの指示内容(プロンプト)を簡単に更新可能
- 新しい改善ルールの追加が容易
- チーム固有のコーディングルールも組み込み可能

#### 今後の展開
- より詳細なコード分析機能の追加
- チーム固有のルールの組み込み
- バッチ処理機能の追加

要確認

formatter.jsで生成A使用しない処理を使用できていない

改善項目

AWS SDK の追加活用

  • CloudWatch Logs: AIの応答ログを収集・分析
  • S3: VBAコードのバージョン管理や履歴保存
  • DynamoDB: 変換履歴や頻出パターンの保存

UI

  • 差分表示機能(基本的なnpmパッケージで実現可能)

vercelで公開

ExpressがNext.jsではないのにスムーズな理由は:

  • Vercelは「Next.jsのためのプラットフォーム」ではなく
  • 「モダンなWebアプリケーションのためのプラットフォーム」
  • ExpressもNode.jsアプリケーションとして自然にサポート
  • @vercel/nodeが複雑な変換を全て自動化

このため、ExpressアプリケーションでもNext.jsと同じようにスムーズにデプロイできるのです。

  1. @vercel/node
ExpressのVercelデプロイ 1. vercel.json追加 @vercel/node で Express を自動変換 2. GitHubにプッシュ コードをGitHubにアップロード 3. 環境変数設定 Vercel管理画面で環境変数を設定 Vercelが自動で処理: SSL証明書・ドメイン割り当て・デプロイ

デプロイフロー

vercel.jsonの作成

vercel.jsonは、Vercelにデプロイする際の設定ファイルです。

vercel.json
{
  "version": 2,
  "builds": [
    {
      "src": "src/index.js",
      "use": "@vercel/node"
    },
    {
      "src": "src/public/**",
      "use": "@vercel/static"
    }
  ],
  "routes": [
    {
      "src": "/prompts",
      "dest": "/src/index.js"
    },
    {
      "src": "/format",
      "dest": "/src/index.js"
    },
    {
      "src": "/api/(.*)",
      "dest": "/src/index.js"
    },
    {
      "src": "/(.+\\.[a-zA-Z]+)$",
      "dest": "/src/public/$1"
    },
    {
      "src": "/(.*)",
      "dest": "/src/public/index.html"
    }
  ]
}
.gitignore
node_modules
.env
.env.local
.DS_Store

GitHubにリポジトリを作成

vercel.com にGitHubアカウントでログイン

GitHubリポジトリをインポート

.envの内容を「Environment Variables」設定

Deployで公開完了