本文へスキップ
Aincarn
← Aincarn Lab

Aincarn Lab / コード生成

コード生成AI比較ログ

同じ小さな実装課題を主要AIに依頼し、完成物の動作、UI、コード品質、要件の満たし方を比較します。生成物はページ内プレビューでも確認できます。

完成物の動作UIの使いやすさコード品質要件の満たし方修正しやすさ
2026-05-31Published
Opus4.8 MaxGPT5.5 非常に高いGemini3.1 Pro(High)

TODOアプリの完成物でコード生成力を比較

同じTODOアプリ課題で、完成物の動作、UI、コード品質、要件充足を比較しました。Opus4.8は追加・完了切替・削除の基本要件を満たし、完成物として最も扱いやすい結果でした。

Opus4.8は基本要件を最も安定して満たした。
GPT5.5は見た目と構成のまとまりがよく、調整ベースとして使いやすい。
Gemini3.1は軽量にまとまる一方、細部の追加確認が必要だった。

Evaluation

出力条件への評価サマリー

条件「ブラウザで使えるシンプルなTODOアプリを作る」に対する各モデルのスコアです。5点満点で、合計が高い順に並べています。

Rubric

同一比較回の相対評価として採点します

5段階・相対評価

点数は同じタスク、同じ出力条件、同じ比較回の中での相対評価です。モデル更新や比較対象が変わると、同じ5点でも水準は変わります。そのため過去ログでは当時の順位、点数、評価理由を保存して、現在の評価と混ざらないようにしています。

Evaluation Focus

  • 指定された機能が実際に動き、完成物として確認できるか。
  • UI、コード品質、要件充足、修正しやすさを分けて評価する。
  • 見た目だけでなく、保存、削除、状態変更などの挙動を重視する。

5

同じ比較回の中で、対象モデルの中では最も条件適合度が高い。別回の5点と同一水準とは限らない。

4

同じ比較回の中で上位水準。明確な強みがあるが、5点モデルと比べると補う点がある。

3

同じ比較回の中で中位水準。使える要素はあるが、複数の評価軸で不足が残る。

2

同じ比較回の中で下位水準。条件の一部は満たすが、主要な用途では選びにくい。

1

同じ比較回の中で最も条件適合度が低い。出力・完成物を大きく見直す必要がある。

評価項目Opus4.8 MaxGPT5.5 非常に高いGemini3.1 Pro(High)
完成物の動作544
UIの使いやすさ554
コード品質554
要件の満たし方543
修正しやすさ544

Data Prompt

比較データ収集用プロンプト

各AIに同じプロンプトを渡し、回答、実行日、モデル名、評価項目を分けて記録します。

あなたはAI比較サイト「Aincarn Lab」の検証対象AIです。
以下の条件で、ブラウザ上でそのまま動かせる小さなWebアプリを作成してください。

課題:
ブラウザで使えるシンプルなTODOアプリを作る

要件:
- タスクを追加できる
- 完了状態を切り替えられる
- 削除できる
- localStorageに保存する
- モバイルでも見やすい

出力条件:
- HTML、CSS、JavaScriptを1つのHTMLファイルにまとめる
- 外部ライブラリ、外部CDN、画像、ビルドツールは使わない
- そのまま.htmlとして保存すれば動くコードを出す
- コード全文を1つのコードブロックで出す

Live Preview

生成アプリを触って比較

sandboxed iframe

各モデルのHTML出力を、ページ内の隔離プレビューとして表示しています。外部通信や親ページへの干渉を避けるため、sandbox付きiframeで実行します。

Preview 1

Opus4.8 Max

Preview 2

GPT5.5 非常に高い

Preview 3

Gemini3.1 Pro(High)

Per-model output

モデル別アウトプット

各モデルの出力を、モデルごとに切り替えて確認できます。内容は要約せず、読みやすい位置で改行だけ補っています。

CL

Model

Opus4.8 Max

Model Output

検証出力

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>シンプルTODO</title>
<style>
:root {
--bg: #f4f5f7;
--card: #ffffff;
--text: #1f2430;
--muted: #8a91a0;
--line: #e6e8ec;
--accent: #3b82f6;
--danger: #ef4444;
--done: #aab2c0;
--shadow: 0 6px 24px rgba(0,0,0,.08);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #14161c;
--card: #1d212b;
--text: #e8eaf0;
--muted: #7b8394;
--line: #2b303c;
--accent: #60a5fa;
--danger: #f87171;
--done: #5a6172;
--shadow: 0 6px 24px rgba(0,0,0,.4);
}
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans",
"Noto Sans JP", Meiryo, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: flex;
justify-content: center;
padding: 24px 14px calc(24px + env(safe-area-inset-bottom));
}
.app {
width: 100%;
max-width: 520px;
}
h1 {
font-size: 1.4rem;
margin: 4px 4px 16px;
display: flex;
align-items: baseline;
gap: 10px;
}
h1 .count {
font-size: .8rem;
font-weight: 600;
color: var(--muted);
}
.input-row {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
#task-input {
flex: 1;
min-width: 0;
font-size: 16px; /* iOSの自動ズーム防止 /
padding: 13px 14px;
border: 1px solid var(--line);
border-radius: 12px;
background: var(--card);
color: var(--text);
outline: none;
}
#task-input:focus { border-color: var(--accent); }
button {
font-family: inherit;
cursor: pointer;
border: none;
}
.add-btn {
flex: 0 0 auto;
font-size: 1rem;
font-weight: 600;
padding: 0 18px;
border-radius: 12px;
background: var(--accent);
color: #fff;
}
.add-btn:active { transform: scale(.97); }
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
li {
display: flex;
align-items: center;
gap: 12px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 12px;
padding: 12px 14px;
box-shadow: var(--shadow);
}
.check {
flex: 0 0 auto;
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid var(--muted);
background: transparent;
display: grid;
place-items: center;
color: #fff;
font-size: 14px;
line-height: 1;
padding: 0;
}
li.done .check {
background: var(--accent);
border-color: var(--accent);
}
.text {
flex: 1;
min-width: 0;
word-break: break-word;
cursor: pointer;
}
li.done .text {
text-decoration: line-through;
color: var(--done);
}
.del {
flex: 0 0 auto;
width: 36px;
height: 36px;
border-radius: 9px;
font-size: 1.2rem;
line-height: 1;
background: transparent;
color: var(--muted);
}
.del:hover { color: var(--danger); background: var(--line); }
.empty {
text-align: center;
color: var(--muted);
padding: 40px 10px;
font-size: .95rem;
}
</style>
</head>
<body>
<main class="app">
<h1>TODO <span class="count" id="count"></span></h1>
<form class="input-row" id="form">
<input id="task-input" type="text" placeholder="やることを入力…"
autocomplete="off" maxlength="200" aria-label="新しいタスク">
<button type="submit" class="add-btn">追加</button>
</form>
<ul id="list" aria-live="polite"></ul>
</main>

<script>
(function () {
"use strict";

var STORAGE_KEY = "simple-todo.v1";
var listEl  = document.getElementById("list");
var countEl = document.getElementById("count");
var inputEl = document.getElementById("task-input");
var formEl  = document.getElementById("form");

var tasks = load();

function uid() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}

function load() {
try {
var data = JSON.parse(localStorage.getItem(STORAGE_KEY));
if (!Array.isArray(data)) return [];
return data
.filter(function (t) { return t && typeof t.text === "string"; })
.map(function (t) {
return { id: String(t.id || uid()), text: t.text, done: !!t.done };
});
} catch (e) {
return [];
}
}

function save() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
} catch (e) { / 保存不可(容量超過など)でも操作は継続 */ }
}

function addTask(text) {
text = text.trim();
if (!text) return;
tasks.push({ id: uid(), text: text, done: false });
save();
render();
}

function toggleTask(id) {
tasks.forEach(function (t) { if (t.id === id) t.done = !t.done; });
save();
render();
}

function deleteTask(id) {
tasks = tasks.filter(function (t) { return t.id !== id; });
save();
render();
}

function render() {
listEl.textContent = "";

if (tasks.length === 0) {
var empty = document.createElement("li");
empty.className = "empty";
empty.style.boxShadow = "none";
empty.style.border = "none";
empty.style.background = "transparent";
empty.textContent = "タスクはまだありません";
listEl.appendChild(empty);
}

tasks.forEach(function (t) {
var li = document.createElement("li");
if (t.done) li.className = "done";

var check = document.createElement("button");
check.type = "button";
check.className = "check";
check.textContent = t.done ? "✓" : "";
check.setAttribute("aria-label", t.done ? "未完了に戻す" : "完了にする");
check.addEventListener("click", function () { toggleTask(t.id); });

var span = document.createElement("span");
span.className = "text";
span.textContent = t.text;            // ← textContentでXSSを防止
span.addEventListener("click", function () { toggleTask(t.id); });

var del = document.createElement("button");
del.type = "button";
del.className = "del";
del.textContent = "×";
del.setAttribute("aria-label", "削除");
del.addEventListener("click", function () { deleteTask(t.id); });

li.appendChild(check);
li.appendChild(span);
li.appendChild(del);
listEl.appendChild(li);
});

var remaining = tasks.filter(function (t) { return !t.done; }).length;
countEl.textContent = tasks.length
? "未完了 " + remaining + " / 全 " + tasks.length
: "";
}

formEl.addEventListener("submit", function (e) {
e.preventDefault();
addTask(inputEl.value);
inputEl.value = "";
inputEl.focus();
});

render();
})();
</script>
</body>
</html>

Log Archive

比較ログ アーカイブ

検証を更新するたびに、個別ページで積み上げています。