やっほー!国内のAI狂いだよ!✨
今日はね、スパゲッティコードと格闘する全エンジニアに捧ぐ「最強の自作ツール」の話!ローカルLLMが爆発四散した末に辿り着いた、Geminiの凄さを語らせて!
ねぇねぇ、他人の書いたPythonコードを読むのって、正直「苦行」じゃない?😇
変数は tmp とか data ばっかりだし、関数はどこから呼ばれてるか謎だし…。
修正した後に「で、結局どこが変わったの?」って聞かれて、diff コマンドの出力を見せても「わからん!」って言われるし…。
「もう無理!AIに全部図解させたい!!」
ってことで、キレ散らかしながら「爆速でコードを図解&比較検証するWebアプリ」をPythonで作ってみたよ!👊
今回はその開発ログと、完成した最強ツールのコードを全公開しちゃうね!
開発のきっかけ:ローカルLLMの限界とGeminiの救済
最初はね、「オレオレ最強AIツール」を作ろうと思って、自宅のPC(ローカル環境)で Gemma 3 とか Llama 3 を動かそうとしたの。
意気揚々と15万文字くらいある巨大なレガシーコードを食わせてみたわけ。
そしたらどうなったと思う?
PCがフリーズして、Pythonが落ちた。
メモリ不足とかそういうレベルじゃなくて、もうトークン数が溢れすぎてAIが白目むいちゃったんだよね(泣)。
「あ、これローカルじゃ無理だわ」って悟った瞬間でした。
そこで降臨したのが、我らが神、Google Gemini (Flashモデル) 様ですよ!!🙌✨
- Groq (Maverick/Scout): 爆速推論で、短いコードを瞬殺する特攻隊長。
- Gemini 3 Flash: 100万トークンも余裕で飲み込む、圧倒的包容力の最終兵器。
この「速度のGroq」×「容量のGemini」を組み合わせた最強の布陣(アーキテクチャ)が完成したのです!
踏み抜いた地雷たち(開発裏話)
完成品を見せる前に、私が踏み抜いた地雷を供養させてw
これを知っておくと、みんなが作る時に絶対役に立つから!
1. Mermaid.js の「Syntax Error」爆弾
AIに「フローチャート書いて!」って頼むと、Mermaid 記法で返してくれるんだけど、コードが短すぎたり複雑すぎたりすると、AIが変な記号(" とか ())を混ぜちゃうんだよね。
その結果がこれ。
▲ 絶望の赤い爆弾マーク。これが出るとやる気なくなるよね…
これを回避するために、プロンプトで「記号を使うな!」「日本語で書け!」って激詰めするハメになりましたw
2. Ollamaライブラリ vs OpenAIライブラリ
最初は import ollama っていう専用ライブラリを使ってたんだけど、GroqもGeminiもOllamaも、実は「OpenAI互換の書き方」で統一できるって気づいちゃったの。
コードの中で if model == "ollama": ... else: ... みたいに分岐書くのダサいじゃん?
だから、今回は全部 openai ライブラリで統一したよ!これぞPythonicな美しさ!✨
完成したツールの全貌
はい、おまたせ!
これが苦労の末に完成した「AI Code Diff Verifier v3.2」です!!
機能紹介
- 単体翻訳モード: わけわかめなコードを、日本語解説+フローチャートにしてくれる。
- 比較検証モード: 修正前と修正後のコードを入れると、変更箇所を赤くハイライトした図を作ってくれる!
- 三段構えのリトライ機能: GroqがコケたらGeminiが助けに来る!
- デバッグ機能: 生のMermaidコードを見たり、ワンクリックでコピーしたりできる。
▼ AIによる爆速解説
▼ 自動生成されたフローチャート
ソースコード全公開
出し惜しみなし!コピペで動くフルコードを置いておくね。
ディレクトリ構成はこんな感じで想定してるよ!
1. main.py (Backend)
FastAPIを使って、GroqとGeminiをOpenAI互換クライアントで操る心臓部だよ。
15万文字対策のために、Gemini Flashを呼び出すロジックも入ってます!
🐍 main.py (クリックで展開)
import os
import re
import webbrowser
import uvicorn
from openai import OpenAI
from dotenv import load_dotenv
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from groq import Groq
# ==========================================
# 🔑 設定 & クライアント定義
# ==========================================
# 自分の環境に合わせてパスを変えてね!
env_path = r"C:\Users\AIGURUI\Python_projects\コード翻訳機\.env"
if os.path.exists(env_path):
load_dotenv(env_path)
# 1. Groq クライアント (いつもの主力部隊)
groq_client = Groq()
# 2. Gemini クライアント (最終兵器・OpenAI互換モード)
# ★ここがミソ!GoogleのサーバーをOpenAIライブラリで叩く設定!
gemini_client = OpenAI(
api_key=os.getenv("GEMINI_API_KEY"), # .envから読み込み
base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
)
app = FastAPI()
templates = Jinja2Templates(directory="templates")
# モデル構成
PRIMARY_MODEL = "meta-llama/llama-4-maverick-17b-128e-instruct" # 第1波
BACKUP_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct" # 第2波
GEMINI_MODEL = "gemini-3-flash-preview" # 最終防衛ライン(最強)
# ==========================================
# 🦅 比較・解析ロジック (Gemini対応版)
# ==========================================
def ask_ai_universal(prompt_content):
""" 通常モードと比較モード共通のAI呼び出し関数 """
# システムプロンプトで「日本語」を強制!
messages = [
{"role": "system", "content": "あなたは優秀なコード解説・比較エージェントです。**回答はすべて必ず「日本語」で行ってください。**"},
{"role": "user", "content": prompt_content}
]
# --- 第1波:Maverick (Groq) ---
try:
print(f"🚀 Maverick で解析中...")
completion = groq_client.chat.completions.create(
model=PRIMARY_MODEL, messages=messages, temperature=0.1, max_tokens=6000
)
return completion.choices[0].message.content
except Exception as e_groq_1:
print(f"⚠️ Maverick 応答なし: {e_groq_1}")
print(f"🔄 Scout に切り替え...")
# --- 第2波:Scout (Groq) ---
try:
completion = groq_client.chat.completions.create(
model=BACKUP_MODEL, messages=messages, temperature=0.1, max_tokens=6000
)
return completion.choices[0].message.content
except Exception as e_groq_2:
print(f"⚠️ Scout も応答なし: {e_groq_2}")
print(f"🌌 最終防衛ライン!Google Gemini ({GEMINI_MODEL}) を召喚!")
# --- 第3波:Gemini (via OpenAI Client) ---
try:
# ここもOpenAIライブラリのまま叩ける!美しい!
completion = gemini_client.chat.completions.create(
model=GEMINI_MODEL,
messages=messages,
temperature=0.1
)
return completion.choices[0].message.content
except Exception as e_gemini:
return f"💥 全滅しました... \nGeminiエラー: {e_gemini}\n※ APIキーやモデル名は合ってる?"
# ==========================================
# 📝 プロンプト生成 (日本語指示マシマシ)
# ==========================================
def create_compare_prompt(code_a, code_b):
# 比較モード用のプロンプト
return f"""
以下の【変更前コード(A)】と【変更後コード(B)】を比較し、3つのセクションを出力してください。
**解説は必ず日本語で書いてください。**
【変更前コード(A)】
```python
{code_a}
```
【変更後コード(B)】
```python
{code_b}
```
--- 出力要件 ---
## 1. 変更点の解説
- **日本語で記述すること。**
- どこがどう変わったか、動作にどう影響するかを簡潔に解説。
- 専門用語少なめ、「〜です」調で。
- 箇条書き(* )の前後は必ず改行を入れること。
## 2. 図解A (変更前)
- 変更前コードのフローチャート。
- ```mermaid ... ``` ブロックで記述。
- graph TD を使用。
- ★重要: ノード内のテキストは**日本語**で。記号(" ' [] ())はエラーになるので使わないこと。
## 3. 図解B (変更後・赤色強調)
- 変更後コードのフローチャート。
- ```mermaid ... ``` ブロックで記述。
- graph TD を使用。
- ★重要: 変更箇所は `style ノードID fill:#ffcccc,stroke:#ff0000,stroke-width:2px;` で強調。
- ノード内のテキストは**日本語**で。
"""
def create_single_prompt(code):
# 単体翻訳モード用のプロンプト
return f"""
このコードを**日本語で**翻訳・解説して!
```python
{code}
```
## 1. 解説
- **日本語で記述すること。**
- 何をするコードか一言で(比喩を使って)。
- 手順を箇条書きで(* の前後は必ず改行を入れること)。
## 2. 図解
- ```mermaid ... ``` ブロックで記述。
- graph TD を使用。
- ★重要: ノード内のテキストは**日本語**で。
- ★重要: ノード内のテキストに記号(" ' [] ())を含めないこと。エラーの原因になります。
- コードが短い場合でも、必ず正しいMermaid構文(A --> B)で書くこと。
"""
# ==========================================
# ✂️ パース処理 (Mermaid抽出&整形)
# ==========================================
def parse_response(full_text):
# 正規表現で ```mermaid ... ``` の中身だけを抜き出す
mermaid_matches = re.findall(r"```mermaid\s*([\s\S]*?)\s*```", full_text)
mermaid_a = mermaid_matches[0].strip() if len(mermaid_matches) > 0 else None
mermaid_b = mermaid_matches[1].strip() if len(mermaid_matches) > 1 else None
# 本文からmermaidコードを削除して、解説文だけにする
clean_text = re.sub(r"```mermaid\s*[\s\S]*?\s*```", "", full_text).strip()
# ★ここが工夫ポイント!
# AIがサボって改行しない箇条書きを、正規表現で無理やり改行させる!
clean_text = re.sub(r'([^\n])\s*(##)', r'\1\n\n\2', clean_text)
clean_text = re.sub(r'([^\n])\s*\*\s', r'\1\n\n* ', clean_text)
clean_text = re.sub(r'([^\n])\s*\*\s', r'\1\n\n* ', clean_text)
return clean_text, mermaid_a, mermaid_b
# ==========================================
# 🌐 ルーティング (FastAPIの基本)
# ==========================================
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
# 最初にアクセスした時の画面表示
return templates.TemplateResponse("index.html", {
"request": request, "result_text": None, "mermaid_a": None, "mermaid_b": None, "mode": "single"
})
@app.post("/", response_class=HTMLResponse)
async def process(
request: Request,
code_input: str = Form(...),
code_input_b: str = Form(None),
mode: str = Form("single")
):
# 解析ボタンが押された時の処理
if mode == "compare" and code_input_b:
prompt = create_compare_prompt(code_input, code_input_b)
else:
prompt = create_single_prompt(code_input)
# AIに問い合わせ
full_response = ask_ai_universal(prompt)
# 結果を分解
clean_text, mermaid_a, mermaid_b = parse_response(full_response)
# 結果をHTMLに渡して表示
return templates.TemplateResponse("index.html", {
"request": request,
"code_input": code_input,
"code_input_b": code_input_b,
"result_text": clean_text,
"mermaid_a": mermaid_a,
"mermaid_b": mermaid_b,
"mode": mode
})
# ==========================================
# 🚀 自動発進システム
# ==========================================
if __name__ == "__main__":
# サーバー起動と同時にブラウザも勝手に開く便利機能!
print("🚀 ブラウザを起動します...")
webbrowser.open("http://127.0.0.1:8000")
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
2. templates/index.html (Frontend)
Tailwind CSS と Mermaid.js をCDNで読み込んでるから、HTMLファイル1つでリッチなUIが作れるんだ。
ドラッグ&ドロップやタブ切り替え、コピーボタンのロジックも全部JavaScriptで実装してるよ!
🎨 templates/index.html (クリックで展開)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>AIコード比較検証機</title>
<!-- Tailwind CSSで見た目を整える -->
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
<!-- Mermaid.jsで図を描画 -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });
</script>
<!-- Marked.jsでMarkdownをHTMLに変換 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
/* スクロールバーやアニメーションのCSS */
.custom-scrollbar::-webkit-scrollbar { width: 8px; }
.custom-scrollbar::-webkit-scrollbar-track { background: #1f2937; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; }
.prose h2 { color: #4ade80 !important; margin-top: 1.5em !important; border-bottom: 1px solid #374151; padding-bottom: 0.5rem; }
.prose ul { color: #d1d5db !important; }
.prose strong { color: #22d3ee !important; }
.fade-in { animation: fadeIn 0.3s ease-out forwards; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
.tab-active { background-color: #374151; border-bottom: 2px solid #22d3ee; color: #22d3ee; }
.tab-inactive { background-color: #1f2937; color: #9ca3af; }
.tab-compare-active { background-color: #451a1a; border-bottom: 2px solid #ef4444; color: #ef4444; }
</style>
</head>
<body class="bg-gray-900 text-white font-mono h-screen flex flex-col overflow-hidden">
<!-- ヘッダー -->
<header class="bg-gray-800 border-b border-gray-700 p-4 shadow-md z-10">
<h1 class="text-2xl font-bold text-cyan-400 flex items-center gap-2">
🚀 AI Code Diff <span class="text-xs text-gray-400 border border-gray-600 px-2 rounded">Verifier v3.2</span>
</h1>
</header>
<!-- メインエリア -->
<div class="flex-grow flex min-h-0 p-4 gap-4">
<!-- 左側:入力フォーム -->
<form action="/" method="post" class="flex flex-col w-1/3 min-w-[350px] gap-0 transition-all duration-300">
<input type="hidden" name="mode" id="mode-input" value="{{ mode }}">
<!-- モード切替タブ -->
<div class="flex text-sm font-bold cursor-pointer">
<div id="btn-single" onclick="setMode('single')" class="flex-1 py-3 text-center rounded-t-lg transition tab-active">
📄 単体翻訳
</div>
<div id="btn-compare" onclick="setMode('compare')" class="flex-1 py-3 text-center rounded-t-lg transition tab-inactive hover:bg-gray-800">
⚖️ 比較検証
</div>
</div>
<!-- 入力エリアコンテナ -->
<div class="flex-grow flex flex-col bg-gray-800 border border-gray-700 rounded-b-lg rounded-tr-lg p-4 gap-4 overflow-y-auto">
<!-- Code A -->
<div class="flex flex-col gap-2 group">
<div class="flex justify-between items-end">
<label class="text-xs text-gray-400 font-bold"><span id="label-a">🖊️ Python Code</span></label>
<label class="cursor-pointer bg-gray-700 hover:bg-gray-600 text-[10px] text-white py-1 px-2 rounded flex items-center gap-1 transition border border-gray-600">
📂 読込 <input type="file" id="file-a" class="hidden" onchange="loadFile(this, 'code_input')">
</label>
</div>
<textarea id="code_input" name="code_input" class="w-full bg-gray-900 border border-gray-600 rounded p-3 text-sm focus:outline-none focus:border-cyan-500 resize-y min-h-[150px] text-gray-300 placeholder-gray-600" placeholder="コードをここに貼り付け...">{{ code_input }}</textarea>
</div>
<!-- Code B -->
<div id="input-b-container" class="hidden flex-col gap-2 fade-in group">
<div class="flex items-center gap-2 py-2">
<div class="h-px bg-gray-600 flex-grow"></div>
<span class="text-xs text-red-400 font-bold">👇 変更後 (Modified)</span>
<div class="h-px bg-gray-600 flex-grow"></div>
</div>
<div class="flex justify-between items-end">
<span class="text-[10px] text-red-300">新しいコード</span>
<label class="cursor-pointer bg-red-900/50 hover:bg-red-900 text-[10px] text-red-100 py-1 px-2 rounded flex items-center gap-1 transition border border-red-800">
📂 読込 <input type="file" id="file-b" class="hidden" onchange="loadFile(this, 'code_input_b')">
</label>
</div>
<textarea id="code_input_b" name="code_input_b" class="w-full bg-gray-900 border border-red-900 rounded p-3 text-sm focus:outline-none focus:border-red-500 resize-y min-h-[150px] text-gray-300 placeholder-gray-600" placeholder="修正後のコードをここに...">{{ code_input_b }}</textarea>
</div>
<div class="flex-grow"></div>
<button type="submit" id="submit-btn" class="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-3 px-4 rounded shadow-lg transition flex items-center justify-center gap-2 mt-2">
⚡ 解析開始
</button>
</div>
</form>
<!-- 右側:結果表示 -->
<div class="flex-grow bg-gray-800 border border-gray-700 rounded-lg flex flex-col overflow-hidden w-2/3">
<div class="bg-gray-700/50 p-3 border-b border-gray-700 flex justify-between items-center">
<h2 class="text-sm font-bold text-green-400">🦅 解析レポート</h2>
{% if mode == 'compare' %}
<span class="text-xs bg-red-900 text-red-200 px-2 py-1 rounded border border-red-700">Compare Mode</span>
{% endif %}
</div>
<div class="p-6 overflow-y-auto flex-grow custom-scrollbar">
{% if result_text %}
<!-- 解説テキスト -->
<div id="markdown-output" class="prose prose-invert max-w-none text-sm text-gray-300 mb-8 border-b border-gray-700 pb-8"></div>
<div id="raw-markdown" style="display:none;">{{ result_text }}</div>
<!-- 図解エリア -->
<div class="grid grid-cols-1 {{ 'lg:grid-cols-2' if mermaid_b else '' }} gap-6">
<!-- 図A -->
<div class="bg-gray-900 rounded-lg p-4 border border-gray-700 flex flex-col">
<h3 class="text-xs font-bold text-gray-400 mb-2 text-center">
{{ 'Before (変更前)' if mermaid_b else 'フローチャート' }}
</h3>
{% if mermaid_a %}
<div class="mermaid text-center flex-grow">{{ mermaid_a }}</div>
<!-- コピーボタン付きフッター -->
<div class="mt-4 border-t border-gray-800 pt-2 flex justify-between items-center">
<details class="text-left">
<summary class="text-[10px] text-gray-600 cursor-pointer hover:text-gray-400 select-none">🔍 生コードを見る</summary>
<pre id="raw-code-a" class="text-[10px] text-gray-500 bg-black p-2 rounded mt-1 overflow-x-auto whitespace-pre-wrap font-mono">{{ mermaid_a }}</pre>
</details>
<button onclick="copyToClipboard('raw-code-a', this)" class="text-[10px] text-cyan-500 hover:text-cyan-300 flex items-center gap-1 border border-gray-700 px-2 py-1 rounded hover:bg-gray-800 transition">
📋 <span class="btn-text">コピー</span>
</button>
</div>
{% else %}
<div class="text-center text-gray-600 text-xs py-10">No Diagram</div>
{% endif %}
</div>
<!-- 図B -->
{% if mermaid_b %}
<div class="bg-gray-900 rounded-lg p-4 border border-red-900/50 relative flex flex-col">
<h3 class="text-xs font-bold text-red-400 mb-2 text-center">After (変更後)</h3>
<div class="absolute top-2 right-2 flex gap-1">
<span class="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span>
<span class="text-[10px] text-red-500">Diff Detected</span>
</div>
<div class="mermaid text-center flex-grow">{{ mermaid_b }}</div>
<div class="mt-4 border-t border-red-900/30 pt-2 flex justify-between items-center">
<details class="text-left">
<summary class="text-[10px] text-red-900 cursor-pointer hover:text-red-400 select-none">🔍 生コードを見る</summary>
<pre id="raw-code-b" class="text-[10px] text-red-900/70 bg-black p-2 rounded mt-1 overflow-x-auto whitespace-pre-wrap font-mono">{{ mermaid_b }}</pre>
</details>
<button onclick="copyToClipboard('raw-code-b', this)" class="text-[10px] text-red-400 hover:text-red-300 flex items-center gap-1 border border-red-900/50 px-2 py-1 rounded hover:bg-red-900/20 transition">
📋 <span class="btn-text">コピー</span>
</button>
</div>
</div>
{% endif %}
</div>
{% else %}
<div class="h-full flex flex-col items-center justify-center text-gray-600 opacity-50 gap-4">
<div class="text-6xl animate-bounce">🧐</div>
<p>ファイルを選択するか、コードを貼り付けてね!</p>
</div>
{% endif %}
</div>
</div>
</div>
<script>
// Markdown変換
const rawText = document.getElementById('raw-markdown');
if (rawText) {
document.getElementById('markdown-output').innerHTML = marked.parse(rawText.innerText);
}
const modeInput = document.getElementById('mode-input');
const inputBContainer = document.getElementById('input-b-container');
const btnSingle = document.getElementById('btn-single');
const btnCompare = document.getElementById('btn-compare');
const submitBtn = document.getElementById('submit-btn');
const labelA = document.getElementById('label-a');
// ファイル読み込み処理
function loadFile(input, targetId) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById(targetId).value = e.target.result;
};
reader.readAsText(file);
}
// クリップボードコピー処理
function copyToClipboard(elementId, btnElement) {
const textToCopy = document.getElementById(elementId).innerText;
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = btnElement.querySelector('.btn-text').innerText;
btnElement.querySelector('.btn-text').innerText = "Copied!";
btnElement.classList.add('text-green-400', 'border-green-400');
setTimeout(() => {
btnElement.querySelector('.btn-text').innerText = originalText;
btnElement.classList.remove('text-green-400', 'border-green-400');
}, 2000);
});
}
// モード切替
function setMode(mode) {
modeInput.value = mode;
if (mode === 'compare') {
inputBContainer.classList.remove('hidden');
inputBContainer.classList.add('flex');
btnSingle.className = "flex-1 py-3 text-center rounded-t-lg transition tab-inactive cursor-pointer hover:bg-gray-700";
btnCompare.className = "flex-1 py-3 text-center rounded-t-lg transition tab-compare-active cursor-default";
labelA.innerText = "🖊️ Code A (変更前)";
submitBtn.classList.replace('bg-cyan-600', 'bg-red-600');
submitBtn.classList.replace('hover:bg-cyan-500', 'hover:bg-red-500');
submitBtn.innerHTML = "🔍 比較検証を開始";
} else {
inputBContainer.classList.add('hidden');
inputBContainer.classList.remove('flex');
btnSingle.className = "flex-1 py-3 text-center rounded-t-lg transition tab-active cursor-default";
btnCompare.className = "flex-1 py-3 text-center rounded-t-lg transition tab-inactive cursor-pointer hover:bg-gray-700";
labelA.innerText = "🖊️ Python Code";
submitBtn.classList.replace('bg-red-600', 'bg-cyan-600');
submitBtn.classList.replace('hover:bg-red-500', 'hover:bg-cyan-500');
submitBtn.innerHTML = "⚡ 解析開始";
}
}
const initialMode = "{{ mode }}";
if (initialMode === "compare") setMode('compare');
else setMode('single');
</script>
</body>
</html>
3. start.bat (Launcher)
これをデスクトップに置いておけば、ワンクリックでツールが起動してブラウザが開くよ!
※ 保存時に文字コードを「ANSI」にするのを忘れずに!
🦇 start.bat (クリックで展開)
@echo off
title AI Code Translator Launcher 🚀
echo ---------------------------------------------------
echo AIコード翻訳機 (Maverick & Scout & Gemini) を起動中...
echo ---------------------------------------------------
cd /d "C:\Users\AIGURUI\Python_projects\コード翻訳機"
python main.py
echo.
echo アプリケーションが終了しました。
pause
まとめ:Geminiしか勝たん!
今回、ローカルLLMの限界を感じたけど、それを補って余りある Gemini Flash の「圧倒的コンテキスト量」 に本当に救われました。
15万文字のコードを投げても、エラーひとつ吐かずに「はい、これですね」って解析結果を返してくるあの頼もしさ。
もう一生ついていきます!!(≧▽≦)
みんなもこのコードを使って、ストレスフリーな開発ライフを送ってね!
以上、国内のAI狂いでした!またね〜👋✨






