2026-01-26 画像と文字の組み合わせ指示機能
画像と文字の組み合わせ指示機能
作成日: 2026-01-26 ステータス: 検討中
要件
全コマンド(plan, research, discuss, fullstack, go, nextjs)で、画像と文字を組み合わせた指示を可能にする。
詳細要件
- 対象コマンド: 全コマンド
- 入力形式: 画像 + 文字(両方の組み合わせ)
- 画像枚数: 2-5枚程度
- 重要度: すべての画像が同等に重要
現状分析
現在のシステム構成
- Arguments: 単一の文字列として扱われる
- ファイル選択: プロジェクト内の既存ファイルのみ(アップロード機能なし)
- Backend: Go (Gin) で Claude CLI を呼び出し
- Frontend: Next.js で SSE ストリーミング
- 画像処理機能: なし
既存の Arguments フロー
- Frontend: CommandForm で文字列として入力
- Frontend: 選択されたファイルパスと文字列を結合
- Backend:
Args stringとして受け取り - Backend: Claude CLI に
-p "/<command> <args>"として渡す
実装アプローチ
アプローチ1: Base64エンコード + マルチパート引数構造(推奨)
概要
- 画像をBase64エンコードしてJSON構造で渡す
- Argumentsを構造化:
{text: string, images: [{name, data, mimeType}]} - Claude APIのvision capabilityを活用
メリット
- Claude APIネイティブの画像サポートを活用
- フロントエンドで画像プレビュー可能
- 複数画像を配列で管理しやすい
- 実装が比較的シンプル
- 既存アーキテクチャとの統合が容易
デメリット
- Base64は元のサイズより約33%増加
- 大きな画像は通信量が増える
- Backend側でClaude CLIへの渡し方を調整必要
実装箇所
- Frontend: 画像アップロードUI、Base64エンコード、構造化されたリクエスト
- Backend: 構造化引数の受け取り、Claude CLIへの変換
- 型定義の変更(CommandRequest)
技術的詳細
Frontend型定義
interface ImageData {
name: string;
data: string; // Base64エンコードされた画像データ
mimeType: string; // "image/jpeg", "image/png" など
}
interface CommandRequest {
project: string;
command: string;
args: string; // テキスト部分
images?: ImageData[]; // 新規追加
}
Backend型定義
type CommandRequest struct {
Project string `json:"project"`
Command string `json:"command"`
Args string `json:"args"`
Images []ImageData `json:"images,omitempty"` // 新規追加
}
type ImageData struct {
Name string `json:"name"`
Data string `json:"data"` // Base64
MimeType string `json:"mimeType"`
}
アプローチ2: ファイルアップロード + 一時ストレージ
概要
- 画像を一時的にサーバーにアップロード
- ファイルパスをArgumentsに含める
- Claude CLIがファイルパスから画像を読み込み
メリット
- 大きな画像でもリクエストサイズが小さい
- 画像の再利用が可能(同じセッション内)
- バックエンド側でファイル操作が容易
デメリット
- 一時ファイルの管理が必要(クリーンアップ)
- アップロードエンドポイントの追加実装
- セキュリティ考慮が必要(ファイル検証、サイズ制限)
- 2段階のリクエスト(アップロード→コマンド実行)
実装箇所
- Frontend: 画像アップロードUI、マルチパートフォーム
- Backend: アップロードエンドポイント、一時ストレージ管理
- Backend: クリーンアップロジック
アプローチ3: ハイブリッド(小さい画像はBase64、大きい画像はアップロード)
概要
- 閾値(例: 500KB)を設定
- 小さい画像: Base64で直接送信
- 大きい画像: アップロードしてパスを送信
メリット
- 両方の長所を活用
- パフォーマンス最適化
デメリット
- 実装が複雑
- テストケースが増える
- メンテナンスコストが高い
実装箇所
- 両アプローチの実装箇所すべて
- サイズ判定ロジック
MVP提案: アプローチ1(Base64 + 構造化引数)
最初のMVPとしてアプローチ1を推奨します。
推奨理由
- Claude APIが画像をネイティブサポート
- 実装がシンプルで、既存アーキテクチャとの統合が容易
- 2-5枚程度の画像なら、Base64のオーバーヘッドは許容範囲
- フロントエンドでプレビューが簡単
- 後から必要に応じてアプローチ2に拡張可能
MVP実装スコープ
Phase 1: 基本機能
- 画像アップロードUI(ドラッグ&ドロップ対応)
- 最大5枚までの画像選択
- 画像プレビュー表示(サムネイル)
- Base64エンコード処理
- 構造化されたCommandRequest送信
Phase 2: Backend対応
- CommandRequest型の拡張(Images フィールド追加)
- 構造化引数の受け取り
- Claude CLIへの画像データ渡し(vision capability活用)
- エラーハンドリング(サイズ超過、不正な形式)
Phase 3: UX改善
- 画像削除機能(個別削除)
- 画像の並び替え(ドラッグ&ドロップ)
- サイズ制限とエラーハンドリング
- 画像圧縮(オプション、1MB超の場合)
- ローディングインジケーター
Claude APIの画像サポート
Claude 3シリーズ(Opus/Sonnet/Haiku)はvision capabilityをサポート:
- 対応形式: JPEG, PNG, GIF, WebP
- サイズ制限: 最大5MB/画像(APIの制限)
- 複数画像を同時に渡すことが可能
- Base64エンコードでの送信をサポート
制約事項
- 画像サイズ制限: 5MB/画像
- 最大枚数: 5枚
- 対応フォーマット: JPEG, PNG, GIF, WebP
- 合計サイズ制限: 20MB(リクエスト全体)
実装の詳細設計
Frontend実装
新規コンポーネント: ImageUploader
// src/components/ImageUploader.tsx
interface ImageUploaderProps {
images: ImageData[];
onImagesChange: (images: ImageData[]) => void;
maxImages?: number; // デフォルト: 5
maxSizePerImage?: number; // デフォルト: 5MB
}
export function ImageUploader({
images,
onImagesChange,
maxImages = 5,
maxSizePerImage = 5 * 1024 * 1024
}: ImageUploaderProps) {
// ドラッグ&ドロップ処理
// ファイル選択処理
// Base64エンコード
// プレビュー表示
// 削除処理
}
CommandForm の拡張
// src/components/CommandForm.tsx
export default function CommandForm() {
const [images, setImages] = useState<ImageData[]>([]);
// 既存のステート
const [projectPath, setProjectPath] = useState("");
const [command, setCommand] = useState("");
const [args, setArgs] = useState("");
const [selectedFile, setSelectedFile] = useState("");
const handleSubmit = async () => {
const request: CommandRequest = {
project: projectPath,
command: command,
args: args,
images: images.length > 0 ? images : undefined,
};
// 送信処理
};
}
Backend実装
handler/command.go の拡張
// internal/handler/command.go
type CommandRequest struct {
Project string `json:"project"`
Command string `json:"command"`
Args string `json:"args"`
Images []ImageData `json:"images,omitempty"`
}
type ImageData struct {
Name string `json:"name"`
Data string `json:"data"`
MimeType string `json:"mimeType"`
}
func (h *CommandHandler) HandleStream(c *gin.Context) {
var req CommandRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 画像のバリデーション
if err := validateImages(req.Images); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// サービス層へ渡す
ctx := c.Request.Context()
resultChan := h.service.ExecuteCommandStream(ctx, req)
// ストリーミング処理
}
func validateImages(images []ImageData) error {
if len(images) > 5 {
return fmt.Errorf("最大5枚まで")
}
for _, img := range images {
// Base64デコード
decoded, err := base64.StdEncoding.DecodeString(img.Data)
if err != nil {
return fmt.Errorf("無効なBase64: %w", err)
}
// サイズチェック(5MB)
if len(decoded) > 5*1024*1024 {
return fmt.Errorf("画像サイズは5MB以下: %s", img.Name)
}
// MIMEタイプチェック
allowedTypes := []string{"image/jpeg", "image/png", "image/gif", "image/webp"}
if !contains(allowedTypes, img.MimeType) {
return fmt.Errorf("非対応の形式: %s", img.MimeType)
}
}
return nil
}
service/claude.go の拡張
// internal/service/claude.go
func (s *ClaudeService) ExecuteCommandStream(
ctx context.Context,
req CommandRequest,
) <-chan CommandResult {
resultChan := make(chan CommandResult)
go func() {
defer close(resultChan)
// 画像がある場合、一時ファイルとして保存
var imagePaths []string
if len(req.Images) > 0 {
for i, img := range req.Images {
path, err := saveImageToTemp(img)
if err != nil {
resultChan <- CommandResult{Error: err}
return
}
imagePaths = append(imagePaths, path)
defer os.Remove(path) // クリーンアップ
}
}
// Claude CLI コマンド構築
prompt := fmt.Sprintf("/%s %s", req.Command, req.Args)
args := []string{
"-p", prompt,
"--output-format", "stream-json",
"--verbose",
"--permission-mode", "bypassPermissions",
}
// 画像パスを追加
for _, path := range imagePaths {
args = append(args, "--image", path)
}
cmd := exec.CommandContext(ctx, "claude", args...)
// 実行処理
}()
return resultChan
}
func saveImageToTemp(img ImageData) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(img.Data)
if err != nil {
return "", err
}
// 拡張子を取得
ext := filepath.Ext(img.Name)
if ext == "" {
ext = mimeTypeToExt(img.MimeType)
}
// 一時ファイル作成
tmpFile, err := os.CreateTemp("", "claude-image-*"+ext)
if err != nil {
return "", err
}
defer tmpFile.Close()
if _, err := tmpFile.Write(decoded); err != nil {
return "", err
}
return tmpFile.Name(), nil
}
func mimeTypeToExt(mimeType string) string {
switch mimeType {
case "image/jpeg":
return ".jpg"
case "image/png":
return ".png"
case "image/gif":
return ".gif"
case "image/webp":
return ".webp"
default:
return ""
}
}
テスト計画
Frontend テスト
- 画像アップロード機能のユニットテスト
- Base64エンコードのテスト
- サイズ制限のテスト
- 複数画像の管理テスト
Backend テスト
- CommandRequest のバリデーションテスト
- 画像データのデコードテスト
- 一時ファイル作成・削除のテスト
- Claude CLI 呼び出しのテスト
インテグレーションテスト
- フロントエンドからバックエンドまでのE2Eテスト
- 実際のClaude APIとの連携テスト
セキュリティ考慮事項
- ファイルサイズ制限: フロントエンド・バックエンド両方で検証
- ファイル形式検証: MIMEタイプとマジックバイトの両方をチェック
- 一時ファイルのクリーンアップ: 処理後は必ず削除
- CSRF保護: 既存のセキュリティ機構を維持
- レート制限: 大量の画像アップロードを防ぐ
今後の拡張可能性
将来的な機能追加
- 画像圧縮の自動化
- より大きな画像のサポート(アプローチ2への移行)
- 画像の編集機能(クロップ、回転)
- 画像のキャッシュ機能
- セッション内での画像再利用
次のステップ
/planで実装計画を作成- Phase 1(基本機能)から実装開始
- 計画確定後、
開発/実装/実装待ち/に移動